[TOC]
类结构
首先ReentrantLock继承自父类Lock,然后有3个内部类,其中中有一个抽象内部类Sync继承AQS,两个内部类NonfairSync和FairSync继承了Sync(Sync重写tryRelease),重写了lock()方法和tryAcquire()方法,分别实现了非公平锁和公平锁。
ReentrantLock默认为非公平锁,如果想创建公平锁,可给构造方法传入参数true
1  | public ReentrantLock() {  | 
ReentrantLock有两个构造方法,无参构造方法默认是创建非公平锁,而传入true为参数的构造方法创建的是公平锁。
非公平锁的实现原理
当我们使用无参构造方法构造的时候即ReentrantLock lock = new ReentrantLock(),创建的就是非公平锁。
1  | public ReentrantLock() {  | 
lock方法获取锁
lock方法调用CAS方法设置state的值,如果state等于期望值0(代表锁没有被占用),那么就将state更新为1(代表该线程获取锁成功),然后执行setExclusiveOwnerThread方法直接将该线程设置成锁的所有者。如果CAS设置state的值失败,即state不等于0,代表锁正在被占领着,则执行acquire(1),即下面的步骤。nonfairTryAcquire方法首先调用getState方法获取state的值,如果state的值为0(之前占领锁的线程刚好释放了锁),那么用CAS设置state的值,设置成功则将该线程设置成锁的所有者,并且返回true。如果state的值不为0,那就调用getExclusiveOwnerThread方法查看占用锁的线程是不是自己,如果是的话那就直接将state + 1,然后返回true。如果state不为0且锁的所有者又不是自己,那就返回false,然后线程会进入到同步队列中。
1  | 作者:冠状病毒biss  | 
tryRelease锁的释放
- 判断当前线程是不是锁的所有者,如果是则进行步骤
2,如果不是则抛出异常。 - 判断此次释放锁后
state的值是否为0,如果是则代表锁没有重入,然后将锁的所有者设置成null且返回true,然后执行步骤3,如果不是则代表锁发生了重入执行步骤4。 - 现在锁已经释放完,即
state=0,唤醒同步队列中的后继节点进行锁的获取。 - 锁还没有释放完,即
state!=0,不唤醒同步队列。 
1  | public void unlock() {  | 
公平锁的实现原理
lock方法获取锁
- 获取状态的
state的值,如果state=0即代表锁没有被其它线程占用(但是并不代表同步队列没有线程在等待),执行步骤2。如果state!=0则代表锁正在被其它线程占用,执行步骤3。 - 判断同步队列是否存在线程(节点),如果不存在则直接将锁的所有者设置成当前线程,且更新状态state,然后返回true。
 - 判断锁的所有者是不是当前线程,如果是则更新状态state的值,然后返回true,如果不是,那么返回false,即线程会被加入到同步队列中
 
通过步骤2实现了锁获取的公平性,即锁的获取按照先来先得的顺序,后来的不能抢先获取锁,非公平锁和公平锁也正是通过这个区别来实现了锁的公平性。
1  | final void lock() {  | 
tryRelease锁的释放
公平锁的释放和非公平锁的释放一样,这里就不重复。
 公平锁和非公平锁的公平性是在获取锁的时候体现出来的,释放的时候都是一样释放的。
ReentrantLock的等待/通知机制
我们知道关键字Synchronized + Object的wait和notify、notifyAll方法能实现等待/通知机制,那么ReentrantLock是否也能实现这样的等待/通知机制,答案是:可以。
 ReentrantLock通过Condition对象,也就是条件队列实现了和wait、notify、notifyAll相同的语义。 线程执行condition.await()方法,将节点1从同步队列转移到条件队列中。
线程执行condition.signal()方法,将节点1从条件队列中转移到同步队列。
因为只有在同步队列中的线程才能去获取锁,所以通过Condition对象的wait和signal方法能实现等待/通知机制。
 代码示例:
1  | ReentrantLock lock = new ReentrantLock();  | 
运行输出:
1  | 线程获取锁----Thread-0  | 
执行的流程大概是这样,线程t1先获取到锁,输出了”线程获取锁—-Thread-0”,然后线程t1调用await方法,调用这个方法的结果就是线程t1释放了锁进入等待状态,等待唤醒,接下来线程t2获取到锁,然输出了”另外一个线程获取到锁—-Thread-1”,同时线程t2调用signal方法,调用这个方法的结果就是唤醒一个在条件队列(Condition)的线程,然后线程t1被唤醒,而这个时候线程t2并没有释放锁,线程t1也就没法获得锁,等线程t2继续执行输出”唤醒线程—-Thread-1”之后线程t2释放锁且输出”另外一个线程释放锁—-Thread-1”,这时候线程t1获得锁,继续往下执行输出了线程被唤醒----Thread-0,然后释放锁输出”线程释放锁—-Thread-0”。
如果想单独唤醒部分线程应该怎么做呢?这时就有必要使用多个Condition对象了,因为ReentrantLock支持创建多个Condition对象,例如:
1  | //为了减少篇幅 仅给出伪代码  | 
这样就实现了部分唤醒的功能。
Condition
和synchronized的对比
- 锁的实现
synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。 - 性能
新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。 - 等待可中断
当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。
ReentrantLock 可中断,而 synchronized 不行。 - 公平锁
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。
synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。 - 锁绑定多个条件
一个 ReentrantLock 可以同时绑定多个 Condition 对象。
使用选择
除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一
种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没
有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。 
可中断
1  | public class hhh {  | 
作者:薛8
链接:https://juejin.im/post/5c95df97e51d4551d06d8e8e
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。