文章目录
- 1. AQS中队列实现
- 1.1. Node介绍
- 1.2. 队列相关属性
- 1.3.方法
- 1.3.1. 设置head compareAndSetHead(Node update)
- 1.3.2. 设置tail compareAndSetTail(Node expect, Node update)
- 1.3.3. 修改node的后驱节点 compareAndSetNext(Node node, Node expect, Node update)
- 1.3.4. 往队列插入节点 enq(final Node node) Inserts node into queue
- 1.3.5. 移除节点 cancelAcquire(Node node)
- 1.3.6. 创建等待者 addWaiter(Node mode)
- 2. AQS 源码解读
- 3. Condition
- 3.3.3. await等待
队列同步器 AbstractQueuedSynchronizer(以下简称同步器),是用来构建锁或者其他同步组件的基础框架,它使用了一个 int 成员变量表示同步状态,通过内置的 FIFO 队列来完成资源获取线程的排队工作
同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中免不了要对同步状态进行更改,这时就需要使用同步器提供的3个方法(getState()、setState(int newState) 和 compareAndSetState(int expect,int update))来进行操作,因为它们能够保证状态的改变是安全的
上面这句话基本上就说明了取锁的原理,如果自己能够通过CAS将状态从0修改为1 则获取到锁 ,重入时将数值累加,释放时将数值减1,当状态变成0时 锁释放
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UVX5bLYW-1577708042891)(https://note.youdao.com/yws/res/42066/6E5042CF2E504E3B85C8A5BD08C1255A)]
AbstractQueuedSynchronizer{
/**
独占式获取同步状态,如果获取成功返回,否则进入等待队列
**/
public final void acquire(int arg)
/**
与 void acquire(int arg) 相同,但是该方法响应中断
**/
public final void acquireInterruptibly(int arg)
/**
在 acquireInterruptibly(int arg) 中增加了超时限制,
如果在超时时间内获取锁成功返回true,否则返回false
**/
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
/**
共享式的获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待
同一时刻可以有多个线程获取到同步状态
**/
public final void acquireShared(int arg)
/**
与 void acquireShared(int arg) 相同,但是该方法响应中断
**/
public final void acquireSharedInterruptibly(int arg)
/**
在 acquireSharedInterruptibly(int arg) 中增加了超时限制
**/
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
/**
独占式的释放同步状态,该方法会在释放同步状态后,将同步队列中第一个节点包含的线程唤醒
**/
public final boolean release(int arg)
/**
共享式的释放同步状态
**/
public final boolean releaseShared(int arg)
/**
获取等待在同步队列上的线程集合
**/
public final Collection<Thread> getQueuedThreads()
/**
需要实现的独占式获取同步状态
**/
protected boolean tryAcquire(int arg)
/**
需要实现的独占式释放同步状态
**/
protected boolean tryRelease(int arg)
/**
需要实现的共享式获取同步状态
**/
protected int tryAcquireShared(int arg)
/**
需要实现的共享式释放同步状态
**/
protected boolean tryReleaseShared(int arg)
}
同步器提供的模板方法基本上分为3类:独占式获取与释放同步状态、共享式获取与释放同步状态和查询同步队列中的等待线程情况
独占锁就是在同一时刻只能有一个线程获取到锁,而其他获取锁的线程只能处于同步队列中等待,只有获取锁的线程释放了锁,后继的线程才能够获取锁
共享锁就是 共享式获取与独占式获取最主要的区别在于同一时刻能否有多个线程同时获取到同步状态
共享锁的最经典的应用场景是读写场景,读取走共享锁,写走独占锁,文件写的时候禁止其他人读写,文件读的时候允许多人读,但是不允许写
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lGbZ67Tf-1577708042893)(https://note.youdao.com/yws/res/42092/C88C644838AB4FDFA358DCC060E28FED)]
左半部分,共享式访问资源时,其他共享式的访问均被允许,而独占式访问被阻塞,右半部分是独占式访问资源时,同一时刻其他访问均被阻塞
1. AQS中队列实现
每一个线程都对应一个Node ,RAS中的队列是用链表实现的
1.1. Node介绍
- int waitStatus
- CANCELLED 线程已取消 1
- SIGNAL 等待唤醒的节点 -1
- CONDITION 线程正在等待condition -2
- PROPAGATE 下一次共享式同步状态获取将会无条件的传播下去 -3
- INITIAL 初始状态 0
- Node prev 前驱节点
- Node next 后驱节点
- Node nextWaiter 等待队列中的后继节点,如果当前节点是共享的 那么将是SHARED常量
- Thread Thread 节点对应的线程
状态变化
- 节点刚建立 状态为 INITIAL 0
- 节点取锁失败时 会在
shouldParkAfterFailedAcquire
方法中将前驱节点设置为SIGNAL
1.2. 队列相关属性
同步器拥有首节点(head)和尾节点(tail),没有成功获取同步状态的线程将会成为节点加入该队列的尾部
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nQ2B5hKc-1577708042895)(https://note.youdao.com/yws/res/42090/53282531D07541C1AF840265CB5CD46A)]
1.2.1. head节点
- 当只有一个线程的时候,会直接取锁成功,不会初始化队列,head也就为null,只有产生竞争时才会初始化队列
- head节点是队列的首节点,它初始化时是空节点。
- head节点代表了获取同步状态成功的节点
- head节点的线程在释放同步状态时,将会唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为head节点
- head节点的前驱节点为null
1.2.2. tail尾节点
- tail节点的后驱节点为null
1.3.方法
这里的方法并不是Node的成员方法,而是AQS操作队列的方法 他们都是通过CAS的方式进行属性替换
1.3.1. 设置head compareAndSetHead(Node update)
通过调用Unsafe的cas方式设置head
private static final long headOffset;
static {
headOffset = unsafe.objectFieldOffset (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
}
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
1.3.2. 设置tail compareAndSetTail(Node expect, Node update)
通过调用Unsafe的cas方式设置tail
private static final long tailOffset;
static {
tailOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
}
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
1.3.3. 修改node的后驱节点 compareAndSetNext(Node node, Node expect, Node update)
private static final long nextOffset;
static {
nextOffset = unsafe.objectFieldOffset(Node.class.getDeclaredField("next"));
}
private static final boolean compareAndSetNext(Node node, Node expect, Node update) {
return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}
1.3.4. 往队列插入节点 enq(final Node node) Inserts node into queue
private Node enq(final Node node) {
for (;;) { // 无限循环直到设置成功为止
Node t = tail; // 拿到尾节点
if (t == null) { // 如果尾节点为null 也就是队列是空队列
if (compareAndSetHead(new Node())){ // 使用cas的方式设置头节点,如果设置成功,头尾节点赋值成同一个节点
tail = head;
}
} else {
node.prev = t; // 将当前的尾部节点设置为 新增节点的前驱节点
if (compareAndSetTail(t, node)) { // 尝试使用cas的方式设置尾节点,如果设置成功,将上一个尾部节点的后驱节点设置为新增节点
t.next = node;
return t;
}
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vagKHKmY-1577708042896)(https://note.youdao.com/yws/res/43124/4CDF2015D98144DBA91B7F5D37C81DCC)]
在 enq(final Node node) 方法中,同步器通过“死循环”+CAS 来保证节点的正确添加
当队列为null时,tail是null,先创建一个空node作为head和tail,然后进入下一次循环
现在head和tail是一样的,设置为tail的后驱节点其实就是设置为head的节点。也就是如果队列为空时,添加一个节点,队列里会有两个节点,一个是空的头节点,一个是要添加的节点。
也有可能线程1初始化了head节点,线程2插入了第二个节点 线程1真正插入的节点并不是head的后驱节点
在“死循环”中只有通过 CAS 将节点设置成为尾节点之后,当前线程才能从该方法返回,否则,当前线程不断地尝试设置
可以看出,enq(final Node node) 方法将并发添加节点的请求通过 CAS 变得“串行化”了
1.3.5. 移除节点 cancelAcquire(Node node)
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null) // 如果节点为null 返回
return;
node.thread = null; // 将当前节点的线程置空
// Skip cancelled predecessors
Node pred = node.prev; //得到前驱节点
// 如果前驱节点状态是CANCELLED 删除前驱节点
// pred变更为前驱节点的前驱节点
// 循环往复把CANCELLED的节点全部删除
while (pred.waitStatus > 0) {
node.prev = pred = pred.prev;
}
Node predNext = pred.next; // 得到pred节点的后驱节点predNext
node.waitStatus = Node.CANCELLED; // 当前节点的状态设置为CANCELLED
if (node == tail && compareAndSetTail(node, pred)) { // 如果node是尾节点,且将pred节点设置为尾节点成功
compareAndSetNext(pred, predNext, null); // 尝试将pred节点的next节点设置为null
} else {
// 如果pred节点不是head,且pred节点是SIGNAL 且 pred节点线程不是null
if (pred != head && changeNodeStatus2SIGNAL(pred) && pred.thread != null ) {
Node next = node.next;
if (next != null && next.waitStatus <= 0) { // 如果node有后驱,且状态不是CANCELLED
compareAndSetNext(pred, predNext, next); // 将node的next设置为pre的后驱结点 ,这样 node就被删除了
}
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
private boolean changeNodeStatus2SIGNAL(Node node){
int ws = node.waitStatus;
if(ws == Node.SIGNAL) {
return true;
}
if(ws <= 0 && compareAndSetWaitStatus(node, ws, Node.SIGNAL)) { // 如果节点的状态小于0且设置状态为SIGNAL
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0) {
compareAndSetWaitStatus(node, ws, 0); // 将状态修改为0
}
Node s = node.next;
if (s == null || s.waitStatus > 0) { // 如果node没有后驱节点或后驱节点为CANNELED状态
s = null;
for (Node t = tail; t != null && t != node; t = t.prev){ 从尾节点往前找,找到node且node可以唤醒就唤醒node 否则直到找到一个可以唤醒的节点 唤醒它
if (t.waitStatus <= 0) { // 如果节点状态不是CANNELED状态,找到可以唤醒的一个节点,
s = t;
}
}
}
if (s != null)
LockSupport.unpark(s.thread);
}
- 找到需要取消节点的前驱不为
CANCEL
的节点 pred - 得到前驱节点的后驱节点
predNext
- 如果node是尾节点,尝试将node的pred设置为尾节点,这样 node就被删除了
- 如果node不是尾节点或者无法将pred设置为尾节点
- 如果前驱节点不是head且前驱节点可以修改为SIGNL,且前驱有thread,执行第ii步,否则执行iii
- 将pred节点的后驱节点设置为node的后驱节点,node被删除
- 使用unparkSuccessor方法将node干掉,由于node的Thread为null所以不会执行unpark
这儿通过CAS自旋的方式来进行节点的更新操作,CAS保证了只有一个线程能够设置成功,且状态一致
1.3.6. 创建等待者 addWaiter(Node mode)
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); // 创建一个node
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {//如果尾节点不为null,尝试将新创建的节点设置为尾节点
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node); //将节点插入队列,准确的说是插入队列尾部
return node;
}
为什么在调用enq的前面会调用那么多方法,感觉就像乐观锁一样,认为一次调用即可成功,所以先尝试设置下,如果成功就返回不用执行enq了
2. AQS 源码解读
为了方便理解 我会把一些代码进行拆解重组,但不会删除源码只会注释
2.1. 普通取锁
该方法对中断不敏感,也就是由于线程获取同步状态失败后进入同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移出
public final void acquire(int arg) {
/*
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){ //尝试获取锁,获取失败,新建线程节点并等待
selfInterrupt();
}
*/
if(!tryAcquire(arg)){ // 如果加锁失败
Node node = addWaiter(Node.EXCLUSIVE); // 为当前线程创建节点并追加到队列 这句话执行完成之后node不一定是队尾
boolean interrupt = acquireQueued(node, arg);
if(interrupt) { // 如果线程中断 ,将当前线程设置为中断状态
Thread.currentThread().interrupt();
}
}
}
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// 为当前线程创建一个节点并通过CAS的方式加入到队列尾部
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); // 创建一个node
Node pred = tail; // 前驱节点为当前尾部节点
if (pred != null) {//如果尾节点不为null,尝试将新创建的节点设置为尾节点
node.prev = pred;
if (compareAndSetTail(pred, node)) { // 尝试将自己设置为队尾,成功返回 失败就执行enq进行添加
pred.next = node;
return node;
}
}
enq(node); //将节点插入队列,准确的说是插入队列尾部
return node;
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); // 得到前驱节点
if (p == head && tryAcquire(arg)) { // 如果前驱节点是头部节点 开始尝试独占获取
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
/*
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
interrupted = true;
}
*/
if (shouldParkAfterFailedAcquire(p, node)) {// 获取锁失败后是否需要park
LockSupport.park(this); // 加锁
if(Thread.interrupted()) { // 如果线程唤醒之后是中断状态,那么还是会去抢锁,抢到锁之后返回 true
interrupted = true;
}
}
}
} finally {
if (failed) { // 如果中间出现任何异常退出 将当前节点取消
cancelAcquire(node);
}
}
}
// 如果node节点的前驱节点是CANCELLED状态,那么将node节点前方的所有CANCELLED状态的节点删除
// 如果返回true 就会park线程 如果返回false 那么将会继续取锁
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; // 获取前驱节点的状态
if (ws == Node.SIGNAL) // 如果前驱节是SIGNAL
return true;
if (ws > 0) { // 如果前驱节点状态是 CANCELLED 取消
do {
node.prev = pred = pred.prev;
// 等价于 pred = pred.prev; node.prev = pred; 这种操作说白了 就是把 A B C 结构修改为A C 删除了 B B在这里是pred
} while (pred.waitStatus > 0); // 循环删除node节点前面的所有CANCELLED状态的节点
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL); // 将前驱节点等待状态设置为SIGNAL
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 加锁
return Thread.interrupted(); // 线程是否中断
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nGBBP6wV-1577708042897)(https://note.youdao.com/yws/res/42094/6D778A534F3D4BE7B8A8D8BAD9549D7B)]
tryAcquire
尝试获取锁,获取成功直接返回 否则执行步骤2- 如果获取锁失败,建立节点并插入同步队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
- 为当前线程创建节点并通过CAS的方式插入队尾
addWaiter(Node.EXCLUSIVE)
- 如果新建节点的前驱节点为头节点,调用
tryAcquire
尝试获取锁 - 如果获取成功,将当前节点设置为头部节点
- 如果获取失败,调用LockSupport.park 线程进入等待状态
- 线程被唤醒,继续抢锁,不管中间是否被中断
第4步很重要,他要解决的问题如下:
假设当前有一个
线程1
已经取到锁且未释放,线程2
线程3
同时取锁,取锁失败,但是在第2步
前,线程1
释放了锁,并去队列中唤醒线程,因为队列为null,唤醒失败。
如果没有
第4步
,线程2
线程3
会进入队列开始等待,可是由于线程1
已经释放,所以不会有线程去唤醒他们了。
当然如果有其他线程过来取锁,它会取锁成功,且在释放锁的时候会唤醒
线程2
线程3
,可是如果没有这个线程,那程序就出现了重大问题了
里面判断前驱节点为头节点也很重要,这样在很多线程进行竞争的时候,可以规避掉其他线程的竞争,只有第二个线程才会进行该操作
2.2. 共享取锁
同步器调用 tryAcquireShared(int arg) 方法尝试获取同步状态,tryAcquireShared(int arg) 方法返回值为 int 类型,当返回值大于等于0时,表示能够获取到同步状态
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0) { // 取锁失败
doAcquireShared(arg);// 执行争抢共享锁
}
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED); // 创建节点并追加到队尾
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); // 得到前驱节点
if (p == head) { // 如果前驱节点是head 尝试取锁
int r = tryAcquireShared(arg); //尝试取锁
if (r >= 0) { 取锁成功
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted) {
selfInterrupt();
}
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) { // 线程暂停挂起
interrupted = true;
}
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node); // 将node设置为头节点
/* 如果有以下的情况,那么回往下一个节点传播状态
传播状态大于0
前后head为空或状态<0
如果节点是共享节点,那么就会释放自己的锁,下一个节点开始抢锁
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
private void setHead(Node node) { // 将node设置为头节点
head = node;
node.thread = null;
node.prev = null;
}
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head; // 得到头节点
if (h != null && h != tail) { // 如果头节点不为null 并且与尾部节点不同
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // 如果头节点状态为SIGNAL
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) { // 将头节点状态设置为0
continue; // loop to recheck cases
}
unparkSuccessor(h); // 解锁节点
} else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) {
continue; // loop on failed CAS
}
}
if (h == head) { // loop if head changed
break;
}
}
}
- 创建节点并加入队列
- 如果前驱节点是头节点,尝试取锁
- 取锁成功后,将自己设置为头节点,将状态外后传播,
- 如果后驱节点是共享节点,释放自己的锁并唤醒后驱节点
- 后驱节点线程进入第2步
2.3. 可中断取锁
public final void acquireInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
if (!tryAcquire(arg)) {
doAcquireInterruptibly(arg);
}
}
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()) {
throw new InterruptedException();
}
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 加锁
return Thread.interrupted(); // 线程是否中断
}
- 尝试加锁失败后执行
doAcquireInterruptibly
- 新建节点并插入尾部
- 如果前节点是头部节点,快速尝试抢锁
- 抢锁失败线程挂起
- 线程被唤醒,如果在沉睡过程中被中断,抛出异常,不再抢锁
2.5. 独占解锁
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();
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0) {
compareAndSetWaitStatus(node, ws, 0); // 将状态修改为0
}
Node s = node.next; // head节点是一个空节点,用不变化,应该是代表了正在持有锁的线程
if (s == null || s.waitStatus > 0) { // 如果node没有后驱节点或后驱节点为CANNELED状态
s = null;
for (Node t = tail; t != null && t != node; t = t.prev){ 从尾节点往前找,找到node且node可以唤醒就唤醒node 否则直到找到一个可以唤醒的节点 唤醒它
if (t.waitStatus <= 0) { // 如果节点状态不是CANNELED状态,找到可以唤醒的一个节点,
s = t;
}
}
}
if (s != null) {
LockSupport.unpark(s.thread);
}
}
- 解锁调用
unlock
->sync.release(1)
- 尝试解锁
tryRelease
,尝试解锁成功,调用unparkSuccessor
进行唤醒操作,解锁失败直接返回false unparkSuccessor
中虽然传入的是头节点,但是后续操作的都是head的后驱节点,因为head代表持有锁的线程,我们需要唤醒的是下一个线程- 如果head后去节点为null或者是取消状态,那么会从队尾往前找,直到找到一个可以唤醒的节点 然后唤醒他
- 被唤醒的线程又会去尝试获取锁,获取成功就会把自己设置成head
理解点
- 线程休眠唤醒使用的是
LockSupport
- 因为有锁的存在,所以一个线程不可能在队列中出现两次
- 因为是持有锁的线程才会解锁,所以可以放心执行,不会有线程安全问题
- 解锁的时head的后驱节点的线程
- 只有头节点不为null,且状态不为0才需要解锁,因为当添加等待节点的时候会把前驱节点的状态从0改为SIGNAL,不为0说明有后驱节点
2.6. 共享解锁
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) { // 如果头节点不为空且不是尾节点
int ws = h.waitStatus; // 得到头节点状态
if (ws == Node.SIGNAL) { // 如果头节点是SIGNAL
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) { // 如果替换失败就继续循环
continue; // loop to recheck cases
} else {
unparkSuccessor(h); // 如果替换成功就unpark线程
}
} else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) // 如果头节点的状态是0,那就死循环尝试释放
continue; // CAS失败时候就进行循环
}
}
if (h == head) { // 如果头被改变就循环
break;
}
}
}
- 首先判断是否有需要唤醒的节点
- 如果头节点是SIGNAL,说明持有锁的线程在执行,那么尝试将状态置为0
- 置0成功说明可以唤醒后驱节点
- 置0失败就自旋重试
- 如果头节点是0,那就尝试将其状态设置为PROPAGATE失败 那么自旋重试
- 如果头发生变化,自旋继续释放锁
3. Condition
Condition 定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到 Condition 对象关联的锁
3.1. 简单使用
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {
lock.lock();
try {
condition.await();// 线程等待
} finally {
lock.unlock();
}
}
public void conditionSignal() throws InterruptedException {
lock.lock();
try {
condition.signal(); // 唤醒
} finally {
lock.unlock();
}
}
当调用 await() 方法后,当前线程会释放锁并在此等待,而其他线程调用 Condition 对象的 signal() 方法,通知当前线程后,当前线程才从 await() 方法返回,并且在返回前已经获取了锁
3.2. Condition接口说明
public interface Condition {
void await() throws InterruptedException;
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void awaitUninterruptibly();
void signal();
void signalAll();
方法 | 说明 |
---|---|
await | 线程进入等待状态直到被通知(signal)或中断 |
awaitUninterruptibly | 线程进入等待状态直到被通知(signal) 但是对中断(interrupt)不敏感 |
awaitNanos | 线程进入等待状态直到被通知、中断或者等待时间超过超时时间,返回值表示剩余时间 |
awaitUntil | 线程进入等待状态直到被通知、中断或者时间到了指定的时间点 |
signal | 唤醒一个等待在Condition上的线程 |
signalAll | 唤醒所有等待在Condition上的线程 |
3.3. 实现代码分析
3.3.1. 属性分析
private transient Node firstWaiter; // 首个等待节点
private transient Node lastWaiter; // 最后一个等待节点
3.3.2. 通用方法
private Node addConditionWaiter() {
Node t = lastWaiter; // 得到最后一个节点
if (t != null && t.waitStatus != Node.CONDITION) { // 如果节点不为空且状态不是CONDITION
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION); // 新建节点 状态为CONDITION
if (t == null) {
firstWaiter = node; // 如果队列尚未初始化,设置为第一个等待节点
} else {
t.nextWaiter = node; // 将新建节点设置为尾节点并连接到队列中
}
lastWaiter = node;
return node;
}
private void unlinkCancelledWaiters() {
Node t = firstWaiter; // 得到首节点
Node trail = null;
while (t != null) { //遍历节点 t为当前节点
Node next = t.nextWaiter; 得到下一个节点
if (t.waitStatus != Node.CONDITION) { // 如果当前节点状态不是CONDITION
t.nextWaiter = null; // 将t的下一个等待节点置空
if (trail == null) {
firstWaiter = next;
} else {
trail.nextWaiter = next;
}
if (next == null) {
lastWaiter = trail;
}
} else{
trail = t;
}
t = next; // 得到节点的下一个节点
}
}
Condition 拥有首尾节点的引用,而新增节点只需要将原有的尾节点 nextWaiter 指向它,并且更新尾节点即可。上述节点引用更新的过程并没有使用 CAS 保证,原因在于调用 await() 方法的线程必定是获取了锁的线程,也就是说该过程是由锁来保证线程安全的
一个对象拥有一个同步队列和等待队列,而并发包中的 Lock(更确切地说是同步器)拥有一个同步队列和多个等待队列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uvQjnMcP-1577708042898)(https://note.youdao.com/yws/res/43324/5A6F16B735CE4DFD993C0E9EBC29E1E3)]
Condition 的实现是同步器的内部类,因此每个 Condition 实例都能够访问同步器提供的方法,相当于每个 Condition 都拥有所属同步器的引用
3.3.3. await等待
public final void await() throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
Node node = addConditionWaiter(); // 当前线程加入等待队列
int savedState = fullyRelease(node); // 释放锁
int interruptMode = 0;
while (!isOnSyncQueue(node)) { // 如果节点不在同步队列
LockSupport.park(this); // 阻塞当前线程
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) {
break;
}
}
// 开始取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE) {
interruptMode = REINTERRUPT;
}
if (node.nextWaiter != null) {// clean up if cancelled
unlinkCancelledWaiters();
}
if (interruptMode != 0) {
reportInterruptAfterWait(interruptMode);
}
}
final boolean isOnSyncQueue(Node node) { // 如果node的状态是CONDITION
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
return findNodeFromTail(node);
}
// AbstractQueuedSynchronizer 的方法 从尾往前调用看能否找到node
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node) {
return true;
}
if (t == null) {
return false;
}
t = t.prev;
}
}
调用该方法的线程成功获取了锁的线程,也就是同步队列中的首节点,该方法会将当前线程构造成节点并加入等待队列中,然后释放同步状态,唤醒同步队列中的后继节点,然后当前线程会进入等待状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kHmRHd1x-1577708042899)(https://note.youdao.com/yws/res/43370/3400307239734A8689B567495962E626)]
同步队列的首节点并不会直接加入等待队列,而是通过 addConditionWaiter() 方法把当前线程构造成一个新的节点并将其加入等待队列中
3.3.4. signal 通知
调用 Condition 的 signal() 方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中
public final void signal() {
if (!isHeldExclusively()) {
throw new IllegalMonitorStateException();
}
Node first = firstWaiter;
if (first != null) {
doSignal(first);
}
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null) {
lastWaiter = null;
}
first.nextWaiter = null;
} while (!transferForSignal(first) && (first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
return false;
}
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) {
LockSupport.unpark(node.thread);
}
return true;
}
调用该方法的前置条件是当前线程必须获取了锁,可以看到 signal() 方法进行了 isHeldExclusively() 检查,也就是当前线程必须是获取了锁的线程。接着获取等待队列的首节点,将其移动到同步队列并使用 LockSupport 唤醒节点中的线程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-22xFttHw-1577708042901)(https://note.youdao.com/yws/res/43384/7F4BB8FDB4ED4C53B6FA16E3D65838F7)]
通过调用同步器的 enq(Node node) 方法,等待队列中的头节点线程安全地移动到同步队列。当节点移动到同步队列后,当前线程再使用 LockSupport 唤醒该节点的线程。
被唤醒后的线程,将从 await() 方法中的 while 循环中退出(isOnSyncQueue(Node node) 方法返回 true,节点已经在同步队列中),进而调用同步器的 acquireQueued() 方法加入到获取同步状态的竞争中。
成功获取同步状态(或者说锁)之后,被唤醒的线程将从先前调用的 await() 方法返回,此时该线程已经成功地获取了锁。
3.3.5. signalAll 通知所有
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
Condition 的 signalAll() 方法,相当于对等待队列中的每个节点均执行一次 signal() 方法,效果就是将等待队列中所有节点全部移动到同步队列中,并唤醒每个节点的线程