Java并发编程|第七篇:Condition
Condition 是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件,只有满足条件时,线程才会被唤醒,功能和使用方法可以类比 wait/notify 。
1.例子
wait/notify
我们先结合synchronized
实现一个简单的 wait/notify
的例子
为了测试方便,将Consumer
作为WaitNotifyDemo
的静态内部类
public class WaitNotifyDemo implements Runnable {
private Object lock;
public WaitNotifyDemo(Object lock) {
this.lock = lock;
}
public static void main(String[] args) {
Object lock = new Object();
new Thread(new WaitNotifyDemo(lock)).start();
new Thread(new Consumer(lock)).start();
}
@Override
public void run() {
synchronized (lock) {
try {
System.out.println(Thread.currentThread().getName() + " >>>> begin - wait");
lock.wait();
System.out.println(Thread.currentThread().getName() + " >>>> end - wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Consumer implements Runnable {
private Object lock;
public Consumer(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " >>>> begin - notify");
lock.notify();
System.out.println(Thread.currentThread().getName() + " >>>> end - notify");
}
}
}
}
执行结果:
Condition
将上面的例子改写成一个Condtion的实现
-
Condition 中的
await
相当于wait
- 阻塞当前线程
-
Condition中 的
single
相当于notify
- 唤醒阻塞的线程
-
Condition中 的
signalAll
相当于notifyAll
- 唤醒所有阻塞的线程
ConditionWait
public class ConditionWait implements Runnable {
private Lock lock;
private Condition condition;
public ConditionWait(Lock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
try {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " >>> begin - conditionWait");
condition.await();
System.out.println(Thread.currentThread().getName() + " >>> end - conditionWait");
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
ConditionNotify
public class ConditionNotify implements Runnable {
private Lock lock;
private Condition condition;
public ConditionNotify(Lock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " >>> begin - conditionNotify");
condition.signal();//唤醒线程
System.out.println(Thread.currentThread().getName() + " >>> end - conditionNotify");
} finally {
lock.unlock();
}
}
}
CondtionDemo
public class CondtionDemo {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
ConditionWait conditionWait = new ConditionWait(lock, condition);
new Thread(conditionWait).start();
ConditionNotify conditionNotify = new ConditionNotify(lock, condition);
new Thread(conditionNotify).start();
}
}
执行结果:
2.源码分析
调用Condition之前,先需要获得Lock
锁,以上面的代码为例,一定会存在一个AQS
的队列,如果线程A
和线程B
同时运行,AQS
的队列可能是如下情况,线程A
获取锁,线程B
加入到AQS
队列,封装成阻塞状态的节点
如果此时ThreadA
调用await()
方法,ThreadA
阻塞并释放锁。
await()
将线程A挂机,并加入到 Condition队列中
public final void await() throws InterruptedException {
if (Thread.interrupted())//线程复位,并返回线程是否被中断过
throw new InterruptedException();
Node node = addConditionWaiter();//创建一个新的节点状态为Condition(-2)
int savedState = fullyRelease(node);//彻底释放锁,如果当前锁存在多次重入,在此方法中会把所有的重入次数归零
int interruptMode = 0;
while (!isOnSyncQueue(node)) {//判断当前节点是否存在AQS队列中
LockSupport.park(this);//如果不存在AQS,挂起当前线程
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//竞争锁成功并且没有中断异常
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;//重新中断
if (node.nextWaiter != null) // 当前节点为Condition队列节点
unlinkCancelledWaiters();//剔除Condition队列节点
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
addConditionWaiter()
将
线程A
添加节点到Condition队列,Condition队列是一个单向链表
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果lastWaiter不为空 并且waitStatus不等于-2
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();//把这个节点从链表中移除
t = lastWaiter;
}
//构建一个Condition链表的Node节点状态为-2的
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
线程A执行完之后会形成下面的Condition队列
假设再来一个线程C也执行await()
方法,节点会继续往下添加
fullyRelease()
彻底释放锁,如果当前锁存在多次重入,在此方法中会把所有的重入次数归零
final int fullyRelease(Node node) {
boolean failed = true;
try {
//获取当前state>0 表示重入多少次
int savedState = getState();
//最终会调用tryRelease 将重入次数归零
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
tryRelease()
通过 getState() - releases 直接使得 c = 0 ,因为releases 传入的是重入次数
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
线程A释放锁,线程B获的锁,此时 AQS 队列如下图
继续往下走
isOnSyncQueue()
判断当前线程是否存在
AQS
队列中,通过下面四种方式1.如果Node节点的
waitStatus
状态 = -2 表示线程在Condition队列2.如果Node节点的
prev
节点为空,那么它只能是head节点
,表示它是获得锁的节点,而不是当前释放锁的节点3.如果Node节点的
next
节点 不为空,只有AQS队列才存在next
和prve
的关系 Condition中的是nextWaiter
4.最后通过
Tail节点
从后往前在AQS队列查找是否存在当前节点
final boolean isOnSyncQueue(Node node) {
//如果节点的waitStatus = -2 表示存在condition队列中 或者 node的上一个节点为null 表示是头节点也不存在AQS队列中
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // 如果下一个节点不为空 只有AQS队列才存在 next和prve的关系 Condition中的是nextWaiter
return true;
//从后往前找当前线程节点是否存在AQS队列中
return findNodeFromTail(node);
}
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
最后通过LockSupport.park(this);
挂起当前线程
此时在看线程B(ConditionNotify)执行逻辑,因为线程B获得了Lock锁
condition.signal()
线程B唤醒当前线程
public final void signal() {
//判断当前线程是否获得了锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)//如果Condition队列的头节点不为空 此时的头节点应该是上一步被添加的线程A的Node节点
doSignal(first);
}
isHeldExclusively()
当前OwnerThread是否等于当前线程
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
doSignal()
private void doSignal(Node first) {
do {
//通过这种方式剔除掉Condition队列中被唤醒的节点线程A
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
transferForSignal()
final boolean transferForSignal(Node node) {
/*
* 如果当前节点的状态=-2 修改为0
* 如果更新失败,只有一种可能就是节点被 CANCELLED 了
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* 将当前节点加入到AQS的队列中,并返回当前节点的上一个节点 原Tail节点
*/
Node p = enq(node);
int ws = p.waitStatus;
//如果原Tail节点的waitStatus的状态>0 或者 CAS 当前节点的状态为-1失败
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);//唤醒当前节点中的线程A
return true;
}
doSingal大概走了三个步骤
1.compareAndSetWaitStatus修改节点状态为0
2.enq(node)将节点添加到AQS队列
3.移除Condition中的节点
线程B执行完,Lock.unlock()
释放锁
线程A获得锁,继续执行
checkInterruptWhileWaiting()
如果当前线程被中断,则调用
transferAfterCancelledWait()
判断后续的处理方式应该抛出异常还是重新中断
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
transferAfterCancelledWait()
Thread.interrupt()
中断线程,unpark()
唤醒 会走第一段逻辑
final boolean transferAfterCancelledWait(Node node) {
//如果当前节点是Condition节点 替换ws为0
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
//将node节点加入到AQS队列中
enq(node);
return true;
}
/*
* 如果当前node不是AQS队列中的节点,循环直到node不是AQS中的节点
*/
while (!isOnSyncQueue(node))
Thread.yield();//让出当前cpu时间片
return false;
}
reportInterruptAfterWait()
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)//如果为THROW_IE 抛出异常
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)//如果是REINTERRUPT 重新中断
selfInterrupt();
}
3.总结
橙色的线表示ThreadWait
执行await()方法的线程
普通黑色的线表示ThreadSignal
线程
1.ThreadWait
线程lock()
方法,获得锁成功,同时ThreadSignal
线程获得锁失败,加入到AQS
队列
2.condition.await()
方法挂起线程,将线程加入到Condition队列
,并释放lock锁
3.ThreadSignal
线程竞争获得lock锁
成功
4.condition.signal()
方法唤醒ThreadWait
线程,将Condtion队列
中的Node节点添加到AQS
队列
5.ThreadWait
线程竞争lock锁
,执行后续程序
4.参考
腾讯课堂->咕泡学院->mic老师->并发编程基础
来源:CSDN
作者:不懂的浪漫
链接:https://blog.csdn.net/xiewenfeng520/article/details/103591658