JUC-AQS-Condition源码详解
如果直接看这个有难度,大家可以看下基础的AQS详解 JUC(一)-AQS源码分析
一、Condition的作用
二、Condition的数据结构
三、核心源码解读
3.1 await()
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
//放弃所有持有的锁
int savedState = fullyRelease(node);
int interruptMode = 0;
//阻塞判断,当前node是不是在同步队列时,不在同步队列那么就park当前线程
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
//如果被中断了,则检测中断
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//这是此节点已经在同步队列中了
//在队列中获取锁,并判断当前的interruptMode不为-1,即不是抛出异常
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
//把中断类型设置为,重新中断,意味在线程获得锁的时候,重新中断线程
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
1.先判断当前线程的中断状态,如果为中断则抛出异常
2.如果不是中断状态,则把当前节点加入Condition队列中,即我们的等待队列中
3.释放当前持有的锁,并记住当前state的值
4.会阻塞在判断当前节点是否在同步队列中,只有当执行了signal(),节点才会被添加回到同步队列中,如果不在队列中,则park当前线程
5.如果在同步队列中,则尝试从队列中获得锁
6.获得锁之后,需要响应不同的中断模式
/**
* 判断是否要抛出中断异常
*/
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
//如果中断模型为THROW_IE,就抛出异常
if (interruptMode == THROW_IE)
throw new InterruptedException();
//如果中断模型为REINTERRUPT,就只把线程标记为中断
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
/**
* Convenience method to interrupt current thread.
* 中断当前线程
*/
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
主流程讲过了,我们重点看下第二步Node node = addConditionWaiter()
1.获取当前Condition的lastWaiter
2.判断lastWaiter的状态,是否为Cancelled
3.如果为Cancelled则处理当前Condition中的节点,清除为Cancelled的节点,并设置新的lastWaiter
4.如果不为Cancelled,则用当前线程新建一个状态为Condition的Node节点
5.判断t(lastWaiter)是否为null,如果为null,则代表当前等待队列中已经没有值了,所以我们把firstWaiter设置为新建的node
6.如果t不为null,那我们就可以把t.nextWaiter设置为node
7.将lastWaiter设置为node
以上逻辑我们可以通过简单的类型来划分一下
上图是从分类角度来阐述不同场景下的不同处理逻
3.2 signal()
public final void signal() {
//判断是否为当前线程是否为占有此锁的线程
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//获得到当前的第一个等待节点
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
具体流程
1.先判断是否为当前线程是否为占有此锁的线程
2.获得此等待队列中第一个等待节点,如果此节点不为null,则doSignal此节点
下面我们具体看看doSignal()方法的实现
1.先判断头结点的下一个节点是否为null,如果为null的话,设置lastWaiter为null
2.如果不为null的话,因为头节点已经获取出来了,自然要把他的nextWaiter设置为null
3.根据当前节点是否可以从Condition状态设置到0(node的初始化状态),把当前节点转移到同步队列中去。
4.判断在同步队列中前节点的状态,判断是否要unpark()当前线程。
//删除或者转移节点到同步队列中,直到获取的节点是不可取消节点,或者null
private void doSignal(Node first) {
do {
//判断当前节点的下一个节点是否为null
if ( (firstWaiter = first.nextWaiter) == null)
//如果为null,则证明此等待队列无数据,把lastWaiter也设置为null
lastWaiter = null;
//first节点需要取出,所以nextWaiter设置为null
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
//将节点从条件队列,转送到同步队列,如果成功的话,返回true
final boolean transferForSignal(Node node) {
/*
* 如果无法改变waiStatus的值,那么当前节点已经被取消
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//把节点插入到队列中,返回node的上一个节点
Node p = enq(node);
int ws = p.waitStatus;
//前节点的状态>0,即为Cancelled状态,或者前节点的状态设置为signal失败
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
//唤醒当前线程
LockSupport.unpark(node.thread);
return true;
}
四、Condition和AQS的关系
Condition是条件队列,那么怎么做到的呢?最终获取锁,还是在同步队列中,那么如何做到让执行中的线程,退出拥有的锁,并等待唤醒呢?java是这么做的,直接搞一个同步队列,保存那些等待唤醒的线程。
await()
head执行await,当前head,生成Condition状态的Node节点,并添加到条件队列中。在条件队列中的node是无法获得到锁的,所以需要一直检测,此节点是否被移动到同步队列中,如果检测到移动到队列中,便会尝试获得锁,就可以继续执行了。
signal()
指定doSignal(firstWaiter)
1.先把当前firstWaiter从条件队列中完整剥离出来
2.将节点追加到同步队列的tail后面
3.处理条件节点的队列,将firstWaiter的指针指向下一个节点
4.判断同步队列中的前一个节点的状态,判断是否可以unpark()当前线程
五、实战
源代码如下,大家可以自行debug,我只取几个典型的场景,给大家说明一下
public class MyCondition {
private static Lock lock = new ReentrantLock();
private static Condition A = lock.newCondition();
private static Condition B = lock.newCondition();
private static int count = 0;
static class ThreadA extends Thread{
@Override
public void run() {
this.setName("ThreadA");
try {
lock.lock();
System.out.println("A加锁");
for (int i = 0; i < 10; i++) {
while (count%2 != 0){
//第一次断点
System.out.println("A.await()之前");
A.await();
System.out.println("A等待之后");
}
System.out.println("A");
count++;
B.signal();
System.out.println("唤醒B");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
static class ThreadB extends Thread{
@Override
public void run() {
this.setName("ThreadB");
try {
lock.lock();
System.out.println("B加锁");
for (int i = 0; i < 10; i++) {
while (count%2 != 1){
B.await();
System.out.println("B等待");
}
System.out.println("B");
//第二次断点
count++;
A.signal();
//第三次断点
System.out.println("唤醒A");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new MyCondition.ThreadA().start();
new MyCondition.ThreadB().start();
}
}
1. 在执行A.await()之前的数据
1.当前持有锁的是ThreadA
2.同步队列中保存着ThreadB
3.A等待队列中无数据
2. A执行await()方法之后,在A.signal()之前,我们看下锁的数据
1.当前持有锁的是ThreadB
2.同步队列的tail数据为空
3.A等待队列的头结点为ThreadA
3.当执行唤醒A之后
1.可以看到当前获得锁的线程是ThreadB
2.ThreadA已经被加入到同步队列中了