ReentrantLock解析
ReentrantLock是Java.util.concurrent包中提供的锁。是Lock接口的默认实现,排他锁(独享锁),相对于Synchronized而言,ReentrantLock提供了更细粒度的加锁方式以及更多的操作方式。
ReentrantLock特点
- 可重入;
- 可中断
- 分为公平锁和非公平锁,默认为非公平锁
它的使用方式为
1 | ReentrantLock lock = new ReentrantLock(); |
可以在new的时候指定使用公平锁还是非公平锁。
默认的构造方法,ReentrantLock()创建的是非公平锁。调用ReentrantLock(bool fair)这个构造方法,传入true,既可以创建一个公平的ReentrantLock锁。
1 | /** |
ReentrantLock类结构
参见下图,简单梳理了以下,ReentrantLock的类结构图
- ReentrantLock实现了Lock,Serializable接口
- 内部使用了内部类Sync来实现主要的加锁、解锁逻辑
- 通过继承了Sync类的NonFairSync和FairSync实现了非公平锁和公平锁的逻辑
- Sync内部类继承了AQS
AQS相关内容可参见Java AQS 解析
ReentrantLock加/解锁流程分析
加锁
1 | public void lock() { |
可以看到,代码就是简简单单一句sync.lock().而这块的sync,根据创建的不同选择,可以是公平锁和非公平锁。那么接下来再看看公平锁和非公平锁是怎么处理这块逻辑的。
公平锁
1 | static final class FairSync extends Sync { |
它也是简简单单一句acquire(1).追踪进去,发现它是调用的AbstractQueuedSynchronizer类中的acquire方法。具体逻辑请参考文章Java AQS 解析
我们看一看公平锁自己实现的tryAcquire();
1 | /** |
它首先获取state的值,如果,state为0,表示当前锁时可用的,然后看看等待队列里面是否还有线程在等着,有就不能尝试获取锁,必须排队;
如果state不为0,则判断当前线程是不是就是已经占用锁的那个独占线程,是的话,修改state,期间还判断下state是否溢出。
非公平锁
非公平锁流程上和公平锁类似,区别就是:
lock的时候先直接lock,失败的话,在尝试重新获取
在tryAcquire的时候,没有判断队列中是否有线程在等待这一步,也就是没有这个过程(!hasQueuedPredecessors() ).
看下它的lock方法
1 | /** |
NonFairSync非公平锁的acquire同FairSync一样,还是调用的父类AQS的acquire,这里不做细解。
它在lock的时候,直接通过CAS操作取设置state为1,如果成功了,则抢占锁成功,直接把独占线程设置为自己,否则,再通过AQS的acquire获取锁。
接下来看下NonFairSync的tryAcquire方法
1 | protected final boolean tryAcquire(int acquires) { |
从上面的代码可以比较出,它和FairSync同样功能的方法是极其类似的,就是少了一句(!hasQueuedPredecessors() )。
解锁
ReentrantLock的解锁方法,unlock也是简单的调用了一下Sync的release方法
1 | /** |
解锁在这块就没有公平和非公平之分了,统一调用的是AQS中的release方法。请参考文章Java AQS 解析
而AQS的release方法中则调用了Sync中实现的tryRelease方法。
1 | protected final boolean tryRelease(int releases) { |
主要流程是:
- 改变状态值
- 如果当前线程不是占有锁的独占线程,抛出异常。只能由占有锁的线程释放锁
- 判断状态值是否已经为0了,是的话,说明锁已经没有线程占用了,自由了
- 设置状态值,返回