Condition相关源码分析

Condition接口定义

Condition将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set (wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。可以通过await(),signal()来休眠/唤醒线程。

Object的监视器方法和Condition接口的特性对比如下

public interface Condition {
// 堵塞等待被唤醒,可被打断
void await() throws InterruptedException;

// 堵塞等待被唤醒,不可被打断
void awaitUninterruptibly();

// 等待指定时间,超时后唤醒,可被打断
long awaitNanos(long nanosTimeout) throws InterruptedException;

// 等待指定时间,超时后唤醒,可被打断
boolean await(long time, TimeUnit unit) throws InterruptedException;

// 等待直到指定时间后唤醒,可被打断
boolean awaitUntil(Date deadline) throws InterruptedException;

// 通知其他和当前锁对象绑定的任一Condition
void signal();

// 通知所有和当前锁对象绑定的任一Condition
void signalAll();
}

1、Condition提供了await()方法将当前线程阻塞,并提供signal()方法支持另外一个线程将已经阻塞的线程唤醒。

2、Condition需要结合Lock使用

3、线程调用await()方法前必须获取锁,调用await()方法时,将线程构造成节点加入等待队列,同时释放锁,并挂起当前线程

4、其他线程调用signal()方法前也必须获取锁,当执行signal()方法时将等待队列的节点移入到同步队列,当线程退出临界区释放锁的时候,唤醒同步队列的首个节点

ConditionObject

ConditionObject实现了Condition接口,定义在AbstractQueuedSynchronizer中,每个ConditionObject包含一个等待队列,队列节点在AQS中已Node定义,条件队列的实现原理可以类比AQS同步队列的同步队列实现。

等待队列的基本结构如下所示: image Condition拥有首尾节点的引用,而新增节点(调用Condition的await()方法)只需要将原有的尾节点nextWaiter指向它,并且更新尾节点即可。

上述节点引用更新的过程并没有使用CAS保证,原因在于调用await()方法的线程必定是获取了锁的线程,也就是说该过程是由锁来保证线程安全的。

同步队列和条件队列的整体关系如下:

1.每次调用Condition.await方法,相当于将同步队列的首节点(获得锁的节点)移动到Condition的等待队列的尾部。

2.有节点调用Condition.signal方法时,则会将等待队列里的首节点唤醒移动到同步队列的尾节点中。

public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
}
堵塞等待实现

在ConditionObject,实现了Condition的多个方法来进行条件堵塞等待,包括:

await()

void awaitUninterruptibly()

awaitNanos(long nanosTimeout)

await(long time, TimeUnit unit)

awaitUntil(Date deadline)

这里主要看await()方法实现,其他实现大同小异:

public final void await() throws InterruptedException {
// 检测被打断,直接抛出
if (Thread.interrupted())
    throw new InterruptedException();
//将当前线程封装成Node加入到等待队列尾部
Node node = addConditionWaiter();
//释放锁
int savedState = fullyRelease(node);
// 记录wait结束后的中断行为,有3个可能值:
// 0. 默认为0,表示不进行中断相关行为
// 1. REINTERRUPT=1:重新interrupt()函数进行中断
// 2. THROW_IE=-1:直接抛出中断异常
int interruptMode = 0;
//判断当前节点是否已经在同步队列中,如果是则退出循环,如果不是就阻塞当前线程
//其他线程如果发出了signal信号之后,会把等待队列的线程移入同步队列,此时就会退出循环,进入下面的重新获取锁的acquireQueued
while (!isOnSyncQueue(node)) {
    LockSupport.park(this);
    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
        break;
}
//其他发出signal信号的线程释放锁之后,该线程被唤醒并重新竞争锁
// acquireQueued是AQS的同步队列竞争锁逻辑实现,判断当前是否为节点,是则尝试获取锁,不是则堵塞直到成为首节点
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
    
    interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
    // 取消node节点后续为取消状态的节点,这里等待状态不是CONDITION都被认为是取消状态
    unlinkCancelledWaiters();
if (interruptMode != 0)
    reportInterruptAfterWait(interruptMode);
}

addConditionWaiter 插入等待队列

在实现开始尝试调用addConditionWaiter将当前线程封装成节点放进等待队列中,实现源码为:

private Node addConditionWaiter() {
// 获取尾部节点
Node t = lastWaiter;
// 如果尾节点被取消,开始等待队列清理取消状态节点
if (t != null && t.waitStatus != Node.CONDITION) {
    // 从头开始遍历。去除状态异常节点
    unlinkCancelledWaiters();
    t = lastWaiter;//t指向最后一个状态正确的节点
}
// 生成一个新节点,状态为CONDITION
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)//列表为空,初始化为第一个节点
    firstWaiter = node;
else // 成为原尾节点的下一个节点
    t.nextWaiter = node;
// 更新尾节点
lastWaiter = node;
return node;
}

// 遍历清理取消状态节点实现
private void unlinkCancelledWaiters() {
// 先记为首节点,t代表遍历过程的每个节点
Node t = firstWaiter;
// trail为遍历到当前节点t前的的最后一个有效节点
Node trail = null;
while (t != null) { 
    // 缓存下一个节点,用于更新t
    Node next = t.nextWaiter;
    // 是否为取消状态
    if (t.waitStatus != Node.CONDITION) {
        // 消除引用关系
        t.nextWaiter = null;
        if (trail == null) // 所有节点无效
            // 更新第一个有效节点为firstWaiter
            firstWaiter = next;
        else
            // 记录有效节点的后置引用关联
            trail.nextWaiter = next;
        if (next == null) // 说明遍历结束,更新尾节点
            lastWaiter = trail;
    }
    else // 更新最后一个有效节点
        trail = t;
    // 更新t,进入下一轮遍历
    t = next;
}
}

fullyRelease释放持有的锁

之前说在调用await方法前,condition对象必须取得锁,如果未取得锁,会抛出异常,这是在fullyRelease中实现的,下面看源码:

final long fullyRelease(Node node) {
// 记录释放状态是否失败,默认为true
boolean failed = true;
try {
    long savedState = getState();
    // 尝试释放,释放成功返回true,否则返回false
    if (release(savedState)) {
        // 记录失败成功
        failed = false;
        return savedState;
    } else {
        // 释放锁失败,说明本来不持有锁,抛出异常
        throw new IllegalMonitorStateException();
    }
} finally {
    // 失败则更新节点为取消状态,待从等待队列中移除
    if (failed)
        node.waitStatus = Node.CANCELLED;
}
}

其中release是AQS的原生实现,这里不再分析。

isOnSyncQueue是否在同步队列中

在将当前线程插入等待队列和释放锁后,会调用isOnSyncQueue判断自己是否在同步队列中,如果在,说明被唤醒,否则继续进行堵塞,isOnSyncQueue实现如下

final boolean isOnSyncQueue(Node node) {
//如果当前线程为condition状态,说明在等待队列中正常等待
// 或者prev为null,prev是在同步队列中才有效的参数,说明当前不是同步队列的节点
if (node.waitStatus == Node.CONDITION || node.prev == null)
    return false;
if (node.next != null) 
    // 如果该节点的next(不是nextWaiter,next指针在CLH队列中指向下一个节点)状态不为null,则该节点一定在CLH队列中
    return true;
// 从同步队列尾节点遍历,看是否在同步队列
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;
}
}   
中断响应处理

在判断isOnSyncQueue为false堵塞被唤醒后会检查线程是否因为中断被唤醒,这是因为LockSupport.park会响应线程中断,但LockSupport.park结束是否因为线程中断(还是唤醒),需要通过中断标识位判断,这在checkInterruptWhileWaiting中完成:

private int checkInterruptWhileWaiting(Node node) {
// 线程是否被中断
// 是则调用transferAfterCancelledWait判断中断处理行为
// 返回true表示需要抛出异常,false表示重新打断
return Thread.interrupted() ?
    (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
    0;
}


final boolean transferAfterCancelledWait(Node node) {
//cas将该节点状态由CONDITION变成0
// 如果成功,调用enq将该节点从CONDITION队列添加到同步队列中(但是在CONDITION队列中的nextWaiter连接并没有取消)
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
    // 更新状态成功,可以安心入队
    enq(node);
    // 这里表示后续要抛出异常
    return true;
}

    // cas失败,则死循环判断直到进入同步队列,退出后走上面逻辑
while (!isOnSyncQueue(node))
    Thread.yield();
// 这里表示后续要调用中断函数进行重新中断
return false;
}
堵塞唤醒

在Condition中,操作唤醒其他Condition对象的函数有signal()和signalAll(),两个方法的实现非常类似:

public final void signal() {
// 如果当前线程没有持有锁,抛异常
if (!isHeldExclusively())
    throw new IllegalMonitorStateException();
// 获取等待队列首节点
Node first = firstWaiter;
if (first != null)
    // 唤醒首节点,移动到同步队列
    doSignal(first);
}


public final void signalAll() {
// 如果当前线程没有持有锁,抛异常
if (!isHeldExclusively())
    throw new IllegalMonitorStateException();
// 获取等待队列首节点
Node first = firstWaiter;
if (first != null)
    // 唤醒所有节点,移动到同步队列
    doSignalAll(first);
}

从上面看到,两个函数的区别分别是doSignal和doSignalAll的调用,下面看看这两个函数的源码实现:

//对等待队列中从首部开始的第一个CONDITION状态的节点,执行transferForSignal操作,将node从等待队列中转换到同步队列队列中,同时修改同步队列中原先尾节点的状态
private void doSignal(Node first) {
do {
	//当前循环将first节点从CONDITION队列transfer到同步队列
	//从CONDITION队列中删除first节点,调用transferForSignal将该节点添加到同步队列中,成功则跳出循环
    if ( (firstWaiter = first.nextWaiter) == null)
        lastWaiter = null;
    first.nextWaiter = null;
} while (!transferForSignal(first) &&
         (first = firstWaiter) != null);
}

// 将CONDITION队列中所有node出队,逐个添加到同步队列末尾,同时修改它们在同步队列中前驱节点的状态,修改为signal成功,则不用在此处唤醒该节点的线程,唤醒工作交给同步队列中的前驱节点,否则需要在此处park当前线程。
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
	//将first节点从CONDITION队列中出队
    Node next = first.nextWaiter;
    first.nextWaiter = null;
	//将first节点在同步队列中入队,同时可能需要执行unpark操作
    transferForSignal(first);
	//更新first的指向
    first = next;
} while (first != null);
}

在两个方法中,都调用了transferForSignal,内部主要实现将一个节点从等待队列移动到同步队列的逻辑,具体代码如下:

// 1. 先尝试将等待队列首节点由CONDITION转换为初始状态,如果失败,返回转义失败,
// 2. 调用enq()将该node添加到同步队列中,成为新的尾节点,返回原来的尾节点
// 3. 若同步队列原先尾节点为CANCELLED或者对原先尾节点CAS设置成SIGNAL失败,则唤醒node节点;若cas失败,说明节点在同步队列总前驱节点已经是signal状态了,唤醒工作交给前驱节点(节省了一次park和unpark操作)
final boolean transferForSignal(Node node) {
//如果CAS失败,则当前节点的状态为CANCELLED
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
    return false;
//enq将node添加到CLH队列队尾,返回node的prev节点p
Node p = enq(node);
int ws = p.waitStatus;
//如果p是一个取消了的节点,或者对p进行CAS设置失败,则唤醒node节点,让node所在线程进入到acquireQueue方法中,重新进行相关操作
//否则,由于该节点的前驱节点已经是signal状态了,不用在此处唤醒await中的线程,唤醒工作留给CLH队列中前驱节点
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
    LockSupport.unpark(node.thread);
return true;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值