Java多线程 - Lock的使用

Contents
  1. 使用ReentrantLock类
    1. 使用ReentrantLock进行同步
    2. 使用Condition实现wait/notify
    3. 公平锁与非公平锁
    4. 几个ReentrantLock常用方法
  2. 使用ReentrantReadWriteLock类

Lock对象也能实现同步的效果,而且在使用上更加方便。

技术点:

  • ReentrantLock类的使用
  • ReentrantReadWriteLock类的使用

使用ReentrantLock类

在Java多线程中,可以使用synchronized关键字来实现线程之间同步互斥,ReentrantLock类也能达到同样的效果,并且在扩展功能上也更加强大,比如具有嗅探锁定、多路分支通知等功能,而且在使用上synchronized更加的灵活。

使用ReentrantLock进行同步

调用ReentrantLock对象的lock()方法获得锁,调用unlock()方法释放锁。

调用lock.lock()代码的线程就持有了“对象监视器”,其他线程只有等待锁被释放时再次争抢。效果和使用synchronized关键字一样,线程之间还是顺序执行的。

使用Condition实现wait/notify

关键字synchronized与wait()和notify()/notifyAll()方法相结合可以实现等待/通知模式,类ReentrantLock也可以实现同样的功能,但需要借助于Condition对象。Condition类具有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里面可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。

Condition和notify方式的区别:

在使用notify()/notifyAll()方法进行通知时,被通知的线程是由JVM随机选择的。但使用ReentrantLock结合Condition类是可以实现“选择性通知”。

而synchronized关键字就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在它一个对象的身上。线程开始notifyAll()时,需要通知所有的WAITING线程,没有选择权,会造成相当大的效率问题。

在调用condition.await()方法出现IllegalMonitorStateException异常出错,解决的办法是在condition.await()方法调用之前调用lock.lock()代码获得同步监视器。

Condition实现wait/notify(等价图):

Object类的方法 Condition类的方法
wait() await()
wait(long timeout) await(long time, TimeUnit unit)
notify() signal()
notifyAll() signalAll()

公平锁与非公平锁

锁Lock分为“公平锁”和“非公平锁”,公平锁获取锁的顺序是按照线程加锁的顺序来分配的,即FIFO。而非公平锁就是一种获取锁的抢占机制,是随机获得锁的,这种方式可能造成某些线程一直拿不到锁。ReentrantLock默认使用的是“非公平锁”。

“非公平锁”性能比“公平锁”高5-10倍,因为公平锁需要在多核的情况下维护一个队列,并且在恢复一个被挂起的线程与该线程真正运行起来之间存在着严重的延迟。

实现方式:

1
new ReentrantLock(boolean isFair)

几个ReentrantLock常用方法

  • getHoldCount():查询当前线程保持此锁定的个数,也就是lock()被调用的次数;
  • getQueueLength():返回正等待获取此锁的线程个数,比如5个线程,1个线程首先执行await()方法,那么调用getQueueLength()方法后的返回值是4,说明有4个线程同时在等待lock的释放;
  • getWaitQueueLength(Condition condition):返回等待与此锁给定条件Condition的线程个数,比如5个线程,每个线程都执行了同一个condition对象的await()方法,则调用getWaitQueueLength(Condition condition)方法时返回的int值是5;
  • hasQueuedThread(Thread thread):查询指定的线程是否正在等待此锁;
  • hasQueuedThreads():查询是否有线程等待此锁;
  • hasWaiters(Condition condition):查询是否有线程正在等待与此锁有关的condition条件;
  • isFair():判断是不是公平锁;
  • isHeldByCurrentThread():查询当前线程是否持有此锁;
  • isLocked():查询此锁是否被任意线程持有。

使用ReentrantReadWriteLock类

类ReentrantLock具有完全互斥排它的效果,即同一时间只有一个线程在执行ReentrantLock.lock()方法后面的任务(同synchronized关键字)。这样做虽然保证了实例变量的线程安全性,但效率确实非常低下的。所以在JDK中提供了一种读写锁ReentrantReadWriteLock类,使用它可以加快运行效率,在某些不需要操作实例变量的方法中,完全可以使用读写锁ReentrantReadWriteLock来提升该方法的运行速度。

读写锁有两个锁:一个是读操作相关的锁,也称为共享锁;另一个是写操作相关的锁,也称为排他锁

也就是多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。在没有线程Thread进行写入操作时,进行读取操作的多个Thread都可以获取该锁,而进行写操作的Thread只有在获取写锁后才能进行写入操作。即多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作。

用法:

1
2
3
4
5
6
7
8
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
try {
lock.readLock().lock();
// lock.writeLock().lock();
} finally {
lock.readLock().unlock();
// lock.writeLock().unlock();
}