在Java多线程编程中,我们经常使用synchronized关键字来实现同步,控制多线程对变量的访问,来避免并发问题。

但是有的时候,synchronized关键字会显得过于沉重,不够灵活。synchronized 方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁。

这个时候Lock出现。

Lock不是Java中的关键字而是 java.util.concurrent.locks 包中的一个接口。下面我们简单介绍一下Lock接口。

一、Lock接口简介

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。

Lock相对于synchronized关键字而言更加灵活,你可以自由得选择我你想要加锁的地方。当然更高的自由度也带来更多的责任。

我们通常会在try…catch模块中使用lock关键字,在finally模块中释放锁。下面是示范代码。

    Lock l = ...; 
     l.lock();
     try {
         // access the resource protected by this lock
     } finally {
         l.unlock();
     }

锁的锁定和释放如果在不同的模块时,需要谨慎编码,确保最后锁一定能够得到释放。

二、Lock接口中定义的方法

1、void lock()
获取锁。如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态

2、void lockInterruptibly()
如果当前线程未被中断,则获取锁。

3、Condition newCondition()
返回绑定到此 Lock 实例的新 Condition 实例。

4、boolean tryLock()
仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false

5、boolean tryLock(long time, TimeUnit unit)
如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。

6、void unlock()
释放锁。在等待条件前,锁必须由当前线程保持。调用 Condition.await() 将在等待前以原子方式释放锁,并在等待返回前重新获取锁。

三、实现类ReentrantLock

Lock接口有三个实现类分别是ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock。后面两个是内部类。

第一个ReentrantLock,在我们平常使用中更为频繁。

ReentrantLock是一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

ReentrantLock的构造方法接受一个可选的公平 参数。当设置为 true 时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程。否则此锁将无法保证任何特定访问顺序。也就是说这里可以设置锁的类型为公平锁还是非公平锁。但是,需要注意的是公平锁的情况下,也不能完全确保公平,它总是趋向公平的情况。

ReentrantLock类中还定义了Lock接口之外的方法,例如int getHoldCount() 、boolean hasQueuedThreads() 、boolean hasWaiters(Condition condition)等等,这些方法可以查询当前锁的状态。

四、代码使用

package com.test;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestLock {
    
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        for(int i=0;i<5;i++){
            ThreadPrintStar threadPrintStar = new TestLock().new ThreadPrintStar(lock);
            threadPrintStar.start();
        }                
    }
    
    /**
     * 打印星星
     * @author LKB
     *
     */
    class ThreadPrintStar extends Thread{
        private Lock lock;
        
        public ThreadPrintStar(Lock lock) {
            // TODO Auto-generated constructor stub
            this.lock = lock;
        }
        
        public void run(){
            lock.lock();
            try {    
                for(int i=0;i<5;i++){
                    if(i%2 == 0)
                        System.out.println("i = " + i + "+++" + Thread.currentThread().getName() +
                                " print: " + "☆☆");
                    else 
                        System.out.println("i = " + i + "+++" + Thread.currentThread().getName() +
                                " print: " + "★★");
                                            
                }
                System.out.println();
                System.out.println();
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }
    
}

下面是程序运行结果,可以看到我们通过Lock实现了同步。

i = 0+++Thread-0 print: ☆☆
i = 1+++Thread-0 print: ★★
i = 2+++Thread-0 print: ☆☆
i = 3+++Thread-0 print: ★★
i = 4+++Thread-0 print: ☆☆


i = 0+++Thread-1 print: ☆☆
i = 1+++Thread-1 print: ★★
i = 2+++Thread-1 print: ☆☆
i = 3+++Thread-1 print: ★★
i = 4+++Thread-1 print: ☆☆


i = 0+++Thread-2 print: ☆☆
i = 1+++Thread-2 print: ★★
i = 2+++Thread-2 print: ☆☆
i = 3+++Thread-2 print: ★★
i = 4+++Thread-2 print: ☆☆


i = 0+++Thread-4 print: ☆☆
i = 1+++Thread-4 print: ★★
i = 2+++Thread-4 print: ☆☆
i = 3+++Thread-4 print: ★★
i = 4+++Thread-4 print: ☆☆


i = 0+++Thread-3 print: ☆☆
i = 1+++Thread-3 print: ★★
i = 2+++Thread-3 print: ☆☆
i = 3+++Thread-3 print: ★★
i = 4+++Thread-3 print: ☆☆