
本文对源码的学习是基于JDK1.8版本.
前言
本文主要角色 : ReentrantLock 、 AbstractQueuedSynchronizer(简称AQS).
ReentrantLock 是一种独占式、可重入的互斥锁,即写-写、写-读、读-写、读-读都是互斥,只能一条线程占用临界资源,且默认为非公平锁,可通过构造器设置为公平锁.ReentrantLock的底层实现是基于AQS独占式去获取锁和释放锁,所以对于理解ReentrantLock我们是有必要学习AQS同步器.
【PS : AQS有两种对同步状态的获取方式 : 独占式(Exclusive,只能一条线程执行) 和 共享式(Share,多个线程可同时执行) , 本文基于ReentrantLock的基础上在独占模型下进行源码分析 】
AbstractQueuedSynchronizer --- 队列同步器
AQS主要维护了一条带有头节点(head)和尾结点(tail)的双向链表的同步队列、一个volatile int state(代表着同步状态)、一个静态内部Node结点类,接下来看看AQS提供的这三种资源的各个作用 :
- state : 代表着同步状态,默认初始化为0,即没有被线程占用状态.该属性AQS提供了三个函数来访问和设置 :
- getState() : 获取state;
- setState(int newState) : 设置state
- compareAndSetState(int expect, int update) : CAS设置state,线程是通过该方法来获取锁
- 静态内部Node结点类 如下图 :

代码如下 :
static final class Node {
// 共享式
static final Node SHARED = new Node();
// 独占式
static final Node EXCLUSIVE = null;
// ------ 结点的四种状态(对应下面waitStatus属性) ------
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
// ------ 同步队列中存放结点(Node)的四种核心属性 ------
// 对应上面四种结点状态
volatile int waitStatus;
// 双向链表的上一结点指针,即前驱结点
volatile Node prev;
// 双向链表的下一结点指针,即后继结点
volatile Node next;
// 线程信息
volatile Thread thread;
// 条件队列的属性,即用到Condition的时候会使用到,这里可以反应出条件队列是一条单向链表
Node nextWaiter;
// 该结点是否为共享式
isShared();
// 获取前驱结点
predecessor()
/** 接下来是三个构造函数 **/
}
结点的几种状态 :
- CANCELLED(1) : 表示线程已取消.
- SIGNAL(-1): 表示后继结点在等待当前结点唤醒.后继结点入队时,会将前继结点的状态更新为SIGNAL(结点的状态是存放在前驱结点的).
- CONDITION(-2) : 表示结点在Condition对象的条件队列上,当其他线程调用了Condition的signal()函数后,CONDITION状态的结点将从条件队列转移到同步队列中,等待获取同步锁.
- PROPAGATE(-3) : 共享模式下,前驱节点不仅会唤醒其后继节点,同时也可能会唤醒后继的后继节点,即传播式的唤醒后继结点
- 0 : 默认初始化的值为0
nextWaiter实现的是单向链表的条件队列,与Condition有关,当调用Condition的await()才会进入对应的Condition对象的条件队列(一个Lock可以有多个Condition);当调用Condition的signal()会进入对应的Condition对象的条件队列进行唤醒操作,而基于独占式和共享式有不同的唤醒操作 :
nextWaiter | 说明 |
---|---|
独占式 | 只唤醒一条线程 |
共享式 | 可能唤醒多条线程 |
- 双向链表实现的同步队列,包含一下特点 :
- 同步队列的头结点是持有锁的.
- 同步队列拥有head结点和tail结点,可快速访问到头尾结点,即出队、入队操作
- 同步队列中存储的结点都是Node结点,一个Node结点对应的就是一条线程信息
- 在独占式模式下,只能一条线程竞争同步状态(state)成功,其他竞争失败的线程会被封装成Node结点从尾部中加入同步队列
- 双向链表实现的同步队列,如下图 :

AQS数据结构 --- 小结
- AQS维护了一条双向链表的同步队列、volatile int state的同步状态、一个静态内部类Node结点,并且同步队列中存放的都是封装线程信息的Node结点,只有head结点是持有锁的;
- AQS对于同步状态的访问操作拥有独占式、共享式两种方式,本文只重点围绕独占式模型,共享式会独立一篇文章;
- Condition知识点没了解过的可暂时跳过(Node结点的nextWaiter属性与Condition有关联,也可跳过该属性).
ReentrantLock --- 源码分析
ReentrantLock可分为非公平、公平的重入锁,默认为非公平锁;ReentrantLock的底层是基于AQS的独占式实现,结合上面AQS知识点的理解,现在我们来深入理解ReentrantLock,接下来我们看看非公平、公平模式下的不同实现思路.
ReentrantLock的抽象接口 Lock , 代码如下:
public interface Lock {
// 获取锁 : 该方法如果锁已被其他线程获取,则进行等待
void lock();
// 获取锁 : 该方法如果锁已被其他线程获取,则会返回false,否则返回true,无需等待
boolean tryLock();
// 超时获取锁 : 同上面的tryLock类似,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false;如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 可中断获取锁 : 如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态
void lockInterruptibly() throws InterruptedException;
// 释放锁 : 持有锁线程释放锁
void unlock();
// 创建一个条件对象,即条件队列
Condition newCondition();
}
本文核心学习常用的 获取锁(即lock()函数) 和 释放锁(即unlock()) 这两个函数 :
// 通过sync同步器获取锁,syn就是AQS了,往下继续看
public void lock() {
sync.lock();
}
// 通过sync同步器释放锁
public void unlock() {
sync.release(1);
}
ReentrantLock的两种锁类型,即非公平锁和公平锁,都是通过构造器来创建,代码如下 :
// 同步器,有两种方式,即非公平和公平
private final Sync sync;
public ReentrantLock() {
// 默认使用非公平同步器
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
// NonfairSync 为非公平锁实现类
static final class NonfairSync extends Sync {
......
}
// FairSync 为公平锁实现类
static final class FairSync extends Sync {
......
}
具体看看Sync , 代码如下 :
// 继承了我们熟悉的AQS同步器
abstract static class Sync extends AbstractQueuedSynchronizer {
// 抽象方法 : 获取锁,具体实现是公平锁实现类和非公平锁实现类
abstract void lock();
// 非公平方式获取锁
final boolean nonfairTryAcquire(int acquires) {
......
}
// 释放锁
protected final boolean tryRelease(int releases){
......
}
// 当前线程是否持有锁
protected final boolean isHeldExclusively(){
......
}
}
ReentrantLock的实现,是通过Sync同步器来维护同步状态(state),我们只需要实现同步状态(state)的获取与释放方式即可,至于具体线程在同步队列、条件队列的维护(如获取锁失败入队、条件队列唤醒出队等操作),AQS已经在低层实现好了,不用我们关心.
非公平锁 --- NonfairSync
非公平独占式获取锁 --- tryAcquire()函数
非公平实现类 代码如下:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// 1. 获取锁
final void lock() {
// 2. CAS成功,表示成功获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 3. 否则,失败,这个函数是AQS提供的
acquire(1);
}
// 最终是执行这个函数 : 这个才是真正的独占式获取锁实现函数
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
接下来看看AQS提供的acquire()函数 ,代码如下 :
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 竞争锁失败,加入同步队列并且挂起,竞争锁期间只要被中断过就需要执行这个函数,该函数表示真正的中断线程
selfInterrupt();
}
总共有四个函数,第一个就是独占式获取锁函数 --- tryAcquire() ,代码如下 :
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
AQS 提供的是一个抛出异常???上面我们说过,AQS只负责底层同步队列、条件队列的维护,至于同步状态state的获取和释放锁是我们自己实现,所以可以推断出这个函数真正实现是在非公平锁实现类 , 如下图 :

点击非公平锁,将跳转到非公平锁实现类的 tryAcquire() 函数,代码如下 :
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
// 真正的非公平锁独占式获取锁函数,是我们自己代码层面实现
final boolean nonfairTryAcquire(int acquires) {
// 1. 获取当前线程
final Thread current = Thread.currentThread();
// 2. 获取当前同步状态state
int c = getState();
// 3. 当前没有线程占用锁
if (c == 0) {
// 4. CAS尝试获取锁
if (compareAndSetState(0, acquires)) {
// 4.1 CAS设置成功,即获取锁成功
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 5. 持有锁线程为当前线程
// 5.1 可重入,+1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 5.2 由于是独占式,所以这里设置同步资源state无需CAS设置
setState(nextc);
return true;
}
// 6. 尝试获取锁失败,返回false
return false;
}
如果上面的独占式获取锁失败,则会执行addWaiter(Node.EXCLUSIVE) 以独占式结点加入到同步队列中, 代码如下 :
private Node addWaiter(Node mode) {
// 1. 为当前线程封装成独占式Node结点
Node node = new Node(Thread.currentThread(), mode);
// 2. 获取同步队列尾结点
Node pred = tail;
// 3.尾结点不为null
if (pred != null) {
// 3.1 当前结点与尾结点prev接上
node.prev = pred;
// 3.2 CAS 设置当前结点为尾结点
if (compareAndSetTail(pred, node)) {
// 3.3 CAS设置当前结点为尾结点成功 设置next字段
pred.next = node;
// 3.4 入队成功
return node;
}
}
// 4. 上面CAS入队失败,继续尝试入队
enq(node);
return node;
}
// 自旋的方式入队,不入队誓不罢休 重点是2 ~ 4 步骤
private Node enq(final Node node) {
// 1. 自旋
for (;;) {
// 1.1 获取tail结点
Node t = tail;
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 重点是这里
// 2. 当前结点于尾结点连接起来(prev)
node.prev = t;
// 3. CAS设置 当前结点为tail,即tail指向当前结点
if (compareAndSetTail(t, node)) {
// 4. CAS设置成功,设置next属性
t.next = node;
return t;
}
// 5. CAS失败,自旋继续入队操作
}
}
}
这里重点聊聊enq()函数的 2 ~ 4步骤中 CAS操作的前后操作(prev和next)会导致同步队列可能出现的问题 :
第 2、3步骤中结合使用并非原子操作,所以如果同时出现两个结点入队,即出现多个结点并发入队,会出现如下图情况 : 总是可以保证成功入队的结点通过prev与队列连接上,换句话说就是通过尾结点的前驱结点向前遍历,总是可以遍历查询整条链表的结点数据.(prev 引用是要可靠的)

第3、4步骤中结合使用并非原子操作,所以如果出现两个结点CAS入队成功,由于设置next属性是在CAS操作之后,所以可能存在tail指针变更,导致有可能存在有一些结点的next属性没设置的情况,如下图 :

【PS : 从上面的图可以得出结论 : prev 是可靠的, 而 next 有时会为 null, 但并不一定真的就没有后继结点,即next是不可靠的;所以通过从 tail 开始反向查找, 借助可靠的 prev 引用来定位到指定的结点!!!】
【PS : 参考链接 : Java AQS unparkSuccessor 方法中for循环从tail开始而不是head的疑问?】
上面的addWaiter() 入队成功后,开始执行 acquireQueued()函数 , 代码如下 :
final boolean acquireQueued(final Node node, int arg) {
// 是否成功竞争到锁
boolean failed = true;
try {
// 等待过程中是否被中断过,竞争锁期间不响应中断,只是一个标识
boolean interrupted = false;
// 自旋
for (;;) {
// 1. 获取前驱结点
final Node p = node.predecessor();
// 2. 如果前驱结点为头节点,执行tryAcquire竞争锁
if (p == head && tryAcquire(arg)) {
// 2.1 竞争锁成功,设置为头结点
setHead(node);
p.next = null; // help GC
failed = false; // 成功竞争到锁
return interrupted; // 返回等待过程中是否被中断过
}
// 3. 如果前驱结点不是头结点,则走这里
// 第一个函数为设置结点状态
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 如果等待过程中被中断过,就将interrupted标记为true
// 从这里也可以看出在竞争锁期间是不响应中断的,即并不会抛出中断异常,只是标识为true
interrupted = true;
}
} finally {
// 竞争锁成功是无需执行这个函数的
if (failed)
// 竞争锁失败,被中断取消竞争锁才会执行这个函数,取消同步队列同步队列中的等待结点
cancelAcquire(node);
}
}
// 设置结点状态,把当前结点状态保存在前驱结点中
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取前驱结点状态
int ws = pred.waitStatus;
// 1. 如果ws状态为SIGNAL 执行parkAndCheckInterrupt()函数挂起当前线程
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
// 2. ws>0 即为取消结点,向前遍历寻找状态不大于0的结点,这个过程会回收这些已取消的结点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 3. ws 既不大于0也不等于-1 CAS设置前驱结点状态为SIGNAL,即当前结点状态保存在前驱结点中
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
// 4. 执行到这里将回到上面自旋中
return false;
}
// 挂起当前线程
private final boolean parkAndCheckInterrupt() {
// 挂起线程
LockSupport.park(this);
// 被唤醒,继续执行,并且查看自己是不是被中断的
return Thread.interrupted();
}
非公平独占式获取锁小结,并且如下图
- 头结点持有同步资源,且next结点会自旋的方式去尝试获取锁(第一次尝试还是失败的话,会被设置为SIGNAL状态,第二次还是尝试失败则挂起了)
- 结点的状态保存在前驱结点中(为什么这样设计?这里先保留这个疑问)
- 链表入队操作是在尾结点中进行,并且链表的next指针不一定是有效的,而prev指针是一定有效的
- 每个结点代表一条线程信息,且结点的nextWaiter指针是要结合Conidtion使用,即条件队列,本文对这个知识点暂不叙述
- 竞争锁失败 --> 入队并且进入阻塞状态 --> 竞争锁成功 ,前两个步骤在竞争锁过程中,是不响应中断的,只是一个标识,而到第三个步骤竞争锁成功,发现是有被中断过,才真正执行中断
- 如果线程1调用lock(),而此时持有锁线程刚好把state设置为0,那么线程1刚好竞争到锁,导致线程1这种后来的线程比存在于同步队列的先来的线程优先获取到锁,这就是不公平性.

【PS : 上图是state 基于非可重入状态】
非公平独占式释放锁 --- tryRelease()函数
ReentrantLock释放锁函数unlock(),直接看代码 :
// 调用同步器的release()
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// 抛出一个异常,同获取锁函数是一个道理的
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
非公平和公平方式的释放锁操作都是执行的是Sync的同一个函数 : tryRelease()
// 释放锁真正执行的函数
protected final boolean tryRelease(int releases) {
// 由于是可重入锁,所以释放一次锁state减少1 (参数传送的是1)
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// state为0 说明真正释放锁了
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 设置state
setState(c);
return free;
}
回到release(),当tryRelease()返回true的时候,则执行unparkSuccessor(h) : 该函数的作用就是执行唤醒后继结点
public final boolean release(int arg) {
// 返回true的话 说明释放锁成功了,看看需不需要唤醒后继结点
if (tryRelease(arg)) {
Node h = head;
// 头结点不为null
// 重点是h.waitStatus != 0这个条件,这个属性记录了下一结点的状态
// 即下一结点如果状态为0的话,是无需执行unparkSuccessor()来唤醒后继结点参与竞争锁的
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// 寻找最靠前可唤醒的结点
private void unparkSuccessor(Node node) {
// 1. 获取头结点状态 ws
int ws = node.waitStatus;
// 2. 如果ws小于0 CAS设置为0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 3. 获取下一结点 s
Node s = node.next;
// 4. s为空 (next是不可靠的) 或者 s结点是无效状态 需要重新寻找一个结点来唤醒
if (s == null || s.waitStatus > 0) {
s = null;
// 4.1 从尾结点开始遍历(prev是有效的),找到最靠前的waitStatus<=0且不是头结点的结点,如果存在该节点则唤醒该结点
for (Node t = tail; t != null && t != node; t = t.prev)
// 4.2 waitStatus <= 0 赋值给s
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 5. 执行唤醒
LockSupport.unpark(s.thread);
}
非公平独占式释放锁小结,并且如下图
非公平独占式释放锁的实现逻辑挺简单的,主要就是设置state为0,然后判断是否需要唤醒后继结点.
- 由于ReentrantLock是一个可重入锁,即state大于1时,我们需要多次(state次)调用unlock()来释放锁,而不是一次,因为通过上面的代码分析,一次的调用只对state减1,并不是真正释放锁,而其他线程通过CAS竞争锁的话总是失败的,最终的结果就是会出现死锁.
- unparkSuccessor() 的4.2步骤的判断条件为 waitStatus <= 0 ,个人一开始认为独占式模式下,该条件为waitStatus < 0 就行了,因为waitStatus == 0 的情况下,是addWaiter()入队操作时候才有的,即初始化状态,所以此时并没有阻塞状态;再者,acquireQueued()函数的第3步骤成功设置为SIGNAL状态才会进行挂起阻塞操作.我自己大概猜测就是 : 独占式和共享式调用的释放锁函数都是同一个函数 : tryRelease() , 所以在共享式模式下 waitStatus == 0 的情况下 是可能处于阻塞状态的,具体的到时候分析共享式模式再做分析.
- 为什么是非公平性的,因为每个线程调用lock()后可以参与竞争锁,这样就会出现虽然同步队列中的结点是先来的,但是后来的线程却能比先来的线程优先竞争到锁的可能,导致了不公平性.

【PS : 上图是state 基于非可重入状态】
公平锁 --- FairSync
公平独占式获取锁 --- tryAcquire()函数
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
// 真正执行的是这个函数
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 1. state == 0 ,尝试获取锁
if (c == 0) {
// 1.1 具体看看hasQueuedPredecessors()
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 2. 可重入
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
我们对于非公平性获取锁已经知道,后来的线程可以参与到竞争锁中,导致后来的线程可能会比先来的线程优先竞争到锁,这就是不公平性;所以,公平性必须保证不能出现这种情况,而 hasQueuedPredecessors() 函数的作用就是体现在这里.
只有当 hasQueuedPredecessors() 返回false时才有资格去竞争锁 ,即当链表为空时(即head和tail都等于null),或者链表只存在一个结点(即head和tail指向同一个结点),你才可以去竞争锁,否则只能乖乖的加入到同步队列;换句话说就是判断队列中有没有相关线程的节点已经在排队了,有则返回true表示线程需要排队,没有则返回false则表示线程无需排队 :
// 第一个条件 : h !=t
// 第二个条件 : s = h.next
// 第三个条件 : s.thread != Thread.currentThread()
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
// 入队操作
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
- 这里tail和head先后获取顺序有个小细节,查了几篇文章都是说链表刚开始初始化的时候防止head.next空指针,具体看看这个连接 ---> AQS深入理解 HASQUEUEDPREDECESSORS源码分析 JDK8 .
具体分析 hasQueuedPredecessors() 的两个判断条件 :
- h != t , 要么返回false或者true
- false : 将短路后面第二个判断条件;即当head 、tail 都为null或者指向同一个结点时,不需要排队,可直接参与竞争锁.
- true : 表示同步队列存在至少两个结点以上,需要判断head的后继结点是否为当前结点
- (s = h.next) == null : 如果head的后继结点为空,则表示当前同步队列至少存在两个结点以上,为什么?结合next不可靠原因,是多个结点并发入队导致的,又因为head!=tail,所以可以确认同步队列至少两个结点以上,此时后面的判断条件将短路.
- 还有一种情况 : 初始化的时候头节点(head),尾节点(tail)都为null,此时头节点指向新的节点,但是还没来得及执行tail=head.这个时候hasQueuedPredecessors(xx)被另一个线程执行了,然后判断h!=t(hNode,tnull),结果为true.若是此时h.next==null,说明同步队列正在初始化,进一步说明有节点正在准备入队,此时整体判断就是 :同步队列里有节点在等待,也是不能参与竞争锁的
- s.thread != Thread.currentThread() : 来到这个判断条件,说明 h.next是不为空的,所以判断是否为当前线程,如果不是,也是不能参与竞争锁的
公平独占式 --- 小结
公平独占锁是与不公平独占锁的区别就是获取锁的方式不同,公平锁保证公平的方式在于 hasQueuedPredecessors() ,即先判断有没有节点(线程)先于当前线程排队等候锁的,若有则当前线程需要排队等候.而其他的都是一样的执行代码.
一图胜千言

扩展 --- 响应中断
基于上面的公平锁和非公平锁的lock()函数在获取锁期间的是不响应中断的,只是对一个布尔类型的变量做标识,等真正获取到锁后,根据这个标识判断是否需要真正中断.Lock接口还提供了一个响应中断的函数 : lockInterruptibly(), 如下 :
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
// AQS 提供的函数
public final void acquireInterruptibly(int arg)
throws InterruptedException {
// 是否被中断过
if (Thread.interrupted())
throw new InterruptedException();
// 获取锁
if (!tryAcquire(arg))
// 真正要看的代码是这个函数
doAcquireInterruptibly(arg);
}
响应中断的函数 : doAcquireInterruptibly() :
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 唯一区别就是这里,lock()是设置标志,而lockInterruptibly()是直接中断了
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
总结
- ReentrantLock 是一个可重入、可公平、非公平的独占锁,底层是基于AQS同步器来维护.
- AQS维护着一条双向链表的同步队列、一个int volatile state(同步资源);head结点是持有锁的,而head.next会以自旋的方式去尝试获取锁(第一次尝试还是失败的话,会被设置为SIGNAL状态,自旋第二次还是尝试失败则挂起了),后面的结点处于挂起阻塞状态,等待被前驱结点唤醒.
- 公平锁和非公平锁的主要区别就是 hasQueuedPredecessors() 保证了先判断同步队列有没有结点(线程)先于当前线程排队等候锁的,若有则当前线程需要加入同步队列排队等候,而不是非公平锁可以直接竞争锁导致了不公平.
结束语
- 原创不易
- 希望看完这篇文章的你有所收获!
相关参考资料
- JDK1.8
- Java并发编程实战【书籍】