1. Condition 组件
在jdk中,synchronized可以实现线程通信,wait/notify 方法;在Condition中提供了多线程协调的通信,可以让某一个线程等待,只有条件满足才能被唤醒;
1.1 Condition使用
package com.pattern.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* Created by chenli on 2019/10/29.
*/
public class ConditionWaitDemo extends Thread {
private Lock lock;
private Condition condition;
public ConditionWaitDemo(Lock lock,Condition condition){
this.lock=lock;
this.condition=condition;
}
@Override
public void run() {
lock.lock();
System.out.println("Thread1 start await");
try {
condition.await();
System.out.println("Thread1 end await");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
singal
package com.pattern.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* Created by chenli on 2019/10/29.
*/
public class ConditionNotifyDemo extends Thread {
private Lock lock;
private Condition condition;
public ConditionNotifyDemo(Lock lock,Condition condition){
this.lock=lock;
this.condition=condition;
}
@Override
public void run() {
lock.lock();
System.out.println("Thread2 start signal");
try {
condition.signal();
System.out.println("Thread2 end signal");
} finally {
lock.unlock();
}
}
}
testDemo
package com.pattern.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by chenli on 2019/10/29.
*/
public class ConditionDemo {
public static void main(String[] args) {
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
new Thread(new ConditionWaitDemo(lock,condition)).start();
new Thread(new ConditionNotifyDemo(lock,condition)).start();
}
}
当调用await方法时,当前线程会阻塞,并且释放锁;condition 对象的 signal 或者 signalall 方法通知并被阻塞 的线程,然后自己执行unlock释放锁,被唤醒的线程获得 之前的锁继续执行,最后释放锁。
1.2Condition源码解析
调用condition需要获取锁,存在一个AQS同步队列,如果两个线程同时运行,则AQS队列中
这个时候线程A调用condition.await方法,会使当前线程加入到等待队列中,并且释放锁,同时线程的状态变为等待状态,
1.1.1 condition.await源码
public final void await() throws InterruptedException {
if (Thread.interrupted())//表示await线程允许被中断
throw new InterruptedException();
Node node = addConditionWaiter();//创建一个新的节点,condition node
int savedState = fullyRelease(node);//释放锁,获取锁的状态,并且唤醒AQS队列中的一个线程
int interruptMode = 0; //如果当前节点没有在同步队列上,即还没有被 signal,则将当前线程阻塞
while (!isOnSyncQueue(node)) {//判断该节点有没有在AQS对类中,没有则挂起
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//当线程唤醒后会尝试获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
// interruptMode != THROW_IE -> 表示这个线程
没有成功将 node 入队,但 signal 执行了 enq 方法让其入
队了.
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
1.1.2 addConditionWaiter
这个方式时把该线程封装成node 放到等待队列中,单项链表
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
1.1.3 图解
1.3 fullyRelease
fullyRelease 是彻底的释放锁,如果当前是重入锁,则需要 释放一次;
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {//释放锁,唤醒下一个同步队列中的节点
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
此时同步队列会触发释放锁和重新竞争锁,ThreadB获取锁
1.4 isOnSyncQueue
isOnSyncQueue 判断当前节点是否在同步队列中,如果当前线程没有同步队列中,说明当前节点没有唤醒去争抢同步锁,需要把当前线程挂机来,知道其他线程调用signal方法;
如果在AQS同步队列,意味着它需要去竞争同步锁去获得 执行程序执行权限 为什么要做这个判断呢?原因是在 condition 队列中的节 点会重新加入到AQS队列去竞争锁。也就是当调用signal 的时候,会把当前节点从condition队列转移到AQS队列
1.4.1 如何判断线程A是否在同步队列中?
- 如果ThreadA的waitStatus的状态为CONDITION,说 明它存在于 condition 队列中,不在 AQS 队列。因为 AQS队列的状态一定不可能有CONDITION
- 如果node.prev为空,说明也不存在于AQS队列,原因 是prev=null在AQS队列中只有一种可能性,就是它是 head节点,head节点意味着它是获得锁的节点。
- 如果 node.next 不等于空,说明一定存在于 AQS 队列 中,因为只有AQS队列才会存在next和prev的关系
- findNodeFromTail,表示从tail节点往前扫描AQS队列,
一旦发现AQS队列的节点和当前节点相等,说明节点一 定存在于AQS队列中
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
return findNodeFromTail(node);
}
1.5 condition.signal
线程B获取了抢占锁的权限,这个时候在 ThreadB 中调用了 Condition 的signal()方法,将会唤醒在等待队列中节点
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
1.5.1 doSignal
从condition队列中的首部开始第一个condition,执行transferForSignal操作,将节点从等待队列转移到AQS队列中,同时修改 AQS 队列中原先尾 节点的状态
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
1.5.2 transferForSignal方法
该方法进行cas修改节点操作,,如果成功,就将这个节 点放到 AQS 队列中,然后唤醒这个节点上的线程。此时, 那个节点就会在 await 方法中苏醒
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);;//调用 enq,把当前节点添加到
AQS 队列。并且返回返回按当前节点的上一个节点,也就是原
tail 节点
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true; //如果 node 的 prev 节点已经是
signal 状态,那么被阻塞的 ThreadA 的唤醒工作由 AQS 队
列来完成
}
checkInterruptWhileWaiting
线程A回复执行后
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);
}
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
如果当前线程被中断,则调用 transferAfterCancelledWait方法判断后续的处理应该是 抛出InterruptedException还是重新中断。
这里需要注意的地方是,如果第一次CAS失败了,则不 能判断当前线程是先进行了中断还是先进行了signal方法 的调用,可能是先执行了signal然后中断,也可能是先执 行了中断,后执行了signal,当然,这两个操作肯定是发 生在CAS之前。这时需要做的就是等待当前线程的node 被添加到AQS队列后,也就是enq方法返回后,返回 false告诉checkInterruptWhileWaiting方法返回 REINTERRUPT(1),后续进行重新中断。
简单来说,该方法的返回值代表当前线程是否在park的 时候被中断唤醒,如果为true表示中断在signal调用之 前,signal还未执行,那么这个时候会根据await的语 义,在await时遇到中断需要抛出 interruptedException,返回true就是告诉 checkInterruptWhileWaiting返回THROW_IE(-1)。 如果返回false,否则表示signal已经执行过了,只需要 重新响应中断即可
总结
Condition 总结
await 和 signal 的总结 ,把前面的整个分解的图再通过一张整体的结构图来表 述,线程awaitThread先通过lock.lock()方法获取锁成功 后调用了condition.await方法进入等待队列,而另一个 线程signalThread通过lock.lock()方法获取锁成功后调用 了condition.signal或者signalAll方法,使得线程 awaitThread能够有机会移入到同步队列中,当其他线程 释放lock后使得线程awaitThread能够有机会获取 lock,从而使得线程awaitThread能够从await方法中退 出执行后续操作。如果awaitThread获取lock失败会直 接进入到同步队列。