前言
ReentrantLock
可重入锁功能与synchronized
类似,用于协调多线程间的同步,并且提供比synchronized
更为丰富的功能,比如可响应中断、锁超时等。ReentrantLock
本身的实现其实较为简单,因为大部分的复杂逻辑方法已经由AQS实现了,它只需要实现少部分的关键方法即可,所以在学习ReentrantLock
之前,个人认为有必要先去了解AQS。
本文将先说明重入锁的含义、公平锁与非公平锁的对比,然后进入ReentrantLock
源码的分析,最后再将其与synchronized
关键字进行对比。
重入锁
可重入指的是同一个线程可以对同一把锁进行重复加锁,比如线程A获取到了锁并进入了临界区,然后调用另一个同样需要该锁的方法时,它可以成功的再次获取该锁,而不会被阻塞住。那么如果锁不可重入会发生什么问题呢?很简单,还是以上面的这个例子,此时线程A再次尝试获取锁时会被阻塞,此时就发生了死锁。
ReentrantLock
和synchronized
关键字一样是可重入的,它的内部通过AQS的state
变量记录同步状态,每当一个线程进行加锁时state++
,而释放锁时state--
。因此,当同一个线程重入该锁时,state
就表示着该线程重入的次数。
公平与非公平
ReentrantLock
是可以设置公平或非公平模式的,事实上,JDK中的许多锁实现都默认为非公平模式。在这里先简单对比一下两种模式的区别:
- 公平锁:公平锁保障了多线程获取锁时的顺序,先到的线程先获取到锁,正常情况下每个线程都能获取到锁
- 非公平锁:非公平锁不保障多线程获取锁时的顺序,也就是后来的线程有可能抢占了前面先来的线程获取锁的机会
公平锁保证了每个线程都能按顺序的获取到锁,而非公平锁则有可能导致前面等待许久的线程不停被后来的线程抢占,从而出现“饥饿”问题。但是从效率上来说,非公平锁会比公平锁高出许多,原因在于唤醒一个线程是需要一定时间的,此时后来的线程可以利用这段时间获取锁并执行代码逻辑,当后来的线程释放完锁后,前面的线程可能正好完全苏醒并成功获取到锁,这就有一个充分的优势:原本因为苏醒而浪费的时间被后来的线程充分利用了,而后来的线程也不会因为进入阻塞而导致线程切换的开销。因此,非公平锁的效率其实是高于公平锁的。
源码分析
了解了重入锁和公平与非公平锁后,接下来进入正式的源码分析阶段。
前面说过,ReentrantLock
其实是基于AQS实现的,那么具体是怎么实现的呢?先来看看它的UML类图:
可以看出,ReentrantLock
的抽象内部类Sync
实现了AQS,而Sync
有两个具体的子类FairSync
和NonfairSync
,从名字就可以看出它们分别表示公平模式和非公平模式。通过构造函数的参数可以决定选择哪种模式,如果不传入参数,则默认为非公平模式:1
2
3
4
5
6
7public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Sync
先看下实现了AQS的抽象内部类,相关方法在下面会介绍,这里先省略:
1 | abstract static class Sync extends AbstractQueuedSynchronizer { |
FairSync
FairSync
继承自Sync
,实现了公平模式的ReentrantLock
的相关逻辑。
1 | final void lock() { |
上过过程总结如下:
- 执行AQS的
acquire()
方法 - 调用
FairSync
实现的tryAcquire()
方法,如果同步状态为0,则判断有没等待时间更长的线程,如果没有的话就成功获取;若同步状态不为0,且当前线程为持锁线程,则重入该锁 - 其它情况,一律返回
false
并将当前线程加入到同步队列,该过程由AQS实现
NonfairSync
NonfairSync
同样继承自Sync
,实现了非公平模式的ReentrantLock
的相关逻辑。
1 | final void lock() { |
对比公平模式的FairSync
和非公平模式的NonfairSync
可以发现,它们的差别其实并不大,主要体现在非公平模式在获取锁时不会先检查前面有没有其它等待的线程,而是直接野蛮式CAS,成则获取锁,败则加入同步队列。
释放锁
释放锁的逻辑比较简单,并且没有公平和非公平之分。
1 | public void unlock() { |
与synchronized的异同
ReentrantLock
和synchronized
都是用于线程的同步控制,它们的共同点是都可重入,并且synchronized
也是非公平锁(ReentrantLock
默认为非公平)。而它们之间的不同主要在于以下几点:
ReentrantLock
响应中断,而synchronized
不响应ReentrantLock
支持超时等待,而synchronized
不支持ReentrantLock
可设置成公平锁,而synchronized
不可以- 发生异常时,
synchronized
会自动释放锁,而ReentrantLock
需要手动释放锁
除此之外,ReentrantLock
还提供了丰富的接口用于获取锁的状态,比如可以通过isLocked()
查询ReentrantLock
对象是否处于绑定状态,也可以通过getHoldCount()
获取ReentrantLock
的加锁次数,也就是重入次数,不过它们的本质都是调用AQS实现的getState()
方法。