概要
ReentrantLock顾名思义是可重入锁,提供了公平性的机制,内部基于AbstractQueuedSynchronizer来实现。
所以ReentrantLock只是AbstractQueuedSynchronizer的一个使用场景的实现。
使用
ReentrantLock的使用相信大家都很熟悉,主要是以下几个方法
1 | //阻塞程序,直到成功的获取锁 |
实现原理
ReentrantLock的实现是基于AbstractQueuedSynchronizer的,如果不清楚可以看AQS的实现。
如果你已经大概了解AQS的实现,那么了解ReentrantLock的实现对于你来说就会显得非常简单了。
ReentrantLock持有一个内部类对象Sync
的实例,上面的lock()
、unlock()
等方法都是调用到sync的方法的
1 | private final Sync sync; |
Sync的实现
所以我们重点看看Sync
的实现
1 | //很重要的一点,继承了AbstractQueuedSynchronizer |
公平/非公平锁的实现
ReentrantLock是提供锁的公平机制的,锁是否公平定义如下
公平锁:新线程进来临界区争夺资源,如果之前有线程在排队,那么它必须紧接着排在队列后面
不公平锁:新线程可以跟已经在排队的线程一起竞争,如果失败也是乖乖排队
ReentrantLock在Sync
的基础上实现了非公平的NonfairSync
和公平的FairSync
看看ReentrantLock的初始化
1 | //默认使用非公平锁 |
NonfairSync
1 | static final class NonfairSync extends Sync { |
FairSync
1 | static final class FairSync extends Sync { |
对比
其实可以看出公平锁和非公平锁的差别在于,非公平锁在尝试获得锁的第一时间去尝试获得锁,这时如果锁没被占有,那么它将会跟排队的第一节节点进行竞争。
非公平锁:效率和性能比较高,适于用有TPS要求的场景,但是会出现“饥饿”问题,即在排队的线程等了很久都没有获得锁
公平锁:保证FIFO,不会出现“饥饿”问题
ReentrantLock默认是非公平锁。
重入的实现
这个已经在上面已经提到过了
1 | //重入处理 |
在获得锁的同时会记录锁的持有者exclusiveOwnerThread
,持有者再次获得锁时则可以不用竞争排队,无条件获得锁,不过锁的状态state
会+1,用来记录重入的次数,因为在unlock()
方法会把state
减一,完全释放锁时state == 0
。
与synchronized区别
ReentrantLock的功能与synchronized
类似,经常拿来比较
相同点
- 互斥、阻塞型的
即只有一个线程获得锁,其他线程必须在阻塞等待 - 可重入性
同一个线程可以再次获得锁,无需等待
不同点
相比来说,ReentrantLock
显得更加“高级”一点。synchronized是Java的关键字,由JVM实现其功能,在字节码插入monitorenter指令和monitorexit指令。ReentrantLock则直接由Java代码实现。synchronized更加底层。
- 持有的对象监视器不同
- ReentrantLock可中断
- ReentrantLock提供公平机制
- ReentrantLock可以绑定多个条件,即Condition对象,提供更加丰富的多线程并发骚操作。
JDK1.6之后synchronize在语义上很清晰,进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等,在性能上并不比Lock
差。
综上,在性能差别不大的情况,根据自己的需求来选择,如果对锁的要求简单的话,可以直接用synchronized,如果复杂同步操作,则选择ReentrantLock。
总结
本文介绍基于AbstractQueuedSynchronizer的ReentrantLock的实现,通过源码可以看出重入性和公平性的实现,并对比了与synchronized的区别,阅读本文后希望大家对ReentrantLock的使用更加了然于心。