AQS-条件模式

AQS 条件模式

条件模式使用示例:

public class Run315 {
    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        ThreadAA aa = new ThreadAA(service,"aa");
        aa.start();

        Thread.sleep(3000);
        service.signal1();
        service.signal2();
    }
}

class Service{
    private Lock lock = new ReentrantLock();
    public Condition condition1 = lock.newCondition();
    public Condition condition2 = lock.newCondition();

    public void await1(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " ---- await1");
            condition1.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void signal1(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " ---- signal1");
            condition1.signal();
        }finally {
            lock.unlock();
        }
    }
    public void await2(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " ---- await2");
            condition2.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void signal2(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " ---- signal2");
            condition2.signal();
        }finally {
            lock.unlock();
        }
    }
}
class ThreadAA extends Thread{
    private Service service;
    private String name;

    public ThreadAA(Service service, String name){
        this.service = service;
        this.name = name;
    }

    @Override
    public void run() {
        service.await1();
        System.out.println(Thread.currentThread().getName() + "  released ---- await1");
        service.await2();
        System.out.println(Thread.currentThread().getName() + "  released ---- await2");

    }
}

运行上述代码,会出现一条 signal 未响应的情况,可以分析下原因

匆忙补充的示例,想验证下一个lock,多个 condition 的情况,本文的重点是源码分析,后续会继续从应用中的问题再回顾源码

Condition 接口中的 await/signal 与 object 中的 wait/notify
在这里插入图片描述
ConditionObject 与 Condition 之间的关系

ConditionObject 是 AQS 的内部类,实现了 Condition 接口,主要有两个属性 firstWaiter,lastWaiter

public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        // 条件队列头结点
        private transient Node firstWaiter;
        // 条件队列队尾结点
        private transient Node lastWaiter;
		// 构造器,延迟初始化,使用时才会初始化条件队列
        public ConditionObject() { }
}

1 await()

public final void await() throws InterruptedException {
	// 1. 若当前线程在调用 await() 方法前已经被中断,直接抛出 InterruptedException() 异常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 2. 将当前线程封装成 Node 对象添加到 条件队列中
    Node node = addConditionWaiter();
    // 3. 释放当前线程所占用的锁,保存当前锁的状态
    int savedState = fullyRelease(node);
    // interruptMode {THROW_IE, REINTERRUPT}
    int interruptMode = 0;
    // 4. 若当前队列不在同步队列中,说明刚执行await()方法,还未被 signal/signalAll 方法唤醒
    while (!isOnSyncQueue(node)) {
    	// 4.1 阻塞线程
        LockSupport.park(this);
        // 4.2 检查线程被唤醒的原因,若是中断被唤醒,则跳出 while 循环
        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);
}

await()执行流程:

  1. 若线程在调用 await()前被中断则直接抛出异常
  2. 调用 addConditionWaiter()封装当前线程到等待队列中
  3. 调用 fullyRelease(node)释放当前线程所占用的锁
  4. 将等待队列中的结点添加到同步队列中【中断或者signal/signalAll
  5. 调用 acquireQueued()到同步队列中竞争资源
  6. 竞争到同步资源后,根据 interruptMode处理中断

1.1 addConditionWaiter()

await() 通过 addConditionWaiter() 将当前线程封装成Node 对象,使之进入条件队列中

private Node addConditionWaiter() {
	// 1. 获取条件队列的队尾结点
    Node t = lastWaiter;
    // 2. 若队尾结点被 cancel,则遍历条件队列
    if (t != null && t.waitStatus != Node.CONDITION) {
    	// 2.1 清除已经取消等待的线程
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    // 3. 将当前线程包装成Node对象放入条件队列
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    // 4.1 若队尾结点为空,此时队列无节点处,设置node为头结点
    if (t == null)
        firstWaiter = node;
    else
    	// 4.2 队尾结点非空,设置队尾的nextWaiter 指向 node 结点
        t.nextWaiter = node;
    // 5. 更新队尾结点为node结点
    lastWaiter = node;
    // 6. 返回新添加到条件队列的结点
    return node;
}

作业:同步队列入队方法 addWaiter() 和 条件队列入队方法 addConditionWaiter() 的对比
在这里插入图片描述
处理流程:

  1. 获取尾结点
    同步队列:tail 等待队列:lastWaiter

  2. 尾结点处理
    同步队列:判断尾结点是否为空,若为空则执行 enq(node)先进行同步队列的初始化再将node添加到队尾
    等待队列:判断尾结点是否为空及是否取消等待,若取消则调用unlinkCancelledWaiters()断开与已取消等待结点的连接

  3. 添加到尾结点
    同步队列:尾结点不为空,CAS尝试添加node入尾,若tail == null 或CAS失败,则在 enq(node)中自旋执行CAS入尾操作
    等待队列:尾结点为空,则设置 firstWaiter = node,否则将node设置为 lastWaiter.nextWaiter

  4. 更新尾结点并返回新添加的结点

问题:为什么结点添加到等待队列中时不需要 CAS 操作?

//await()
// 2. 将当前线程封装成 Node 对象添加到 条件队列中
Node node = addConditionWaiter();
// 3. 释放当前线程所占用的锁,保存当前锁的状态
int savedState = fullyRelease(node);

线程执行 await()方法时,还未释放资源,即线程正持有锁,不存在竞争关系

1.1.1 unlinkCancelledWaiters()

若通过 addConditionWaiter() 方法进入条件队列时尾结点可能处于取消状态,新节点不应该连接在已经取消的结点后面,可通过 unlinkCancelledWaiters() 剔除那些已经取消等待的线程

unlinkCancelledWaiters() 从头结点开始遍历整个队列,剔除掉waitStatus != Node.CONDITION 的结点

问题:addConditionWaiter()添加新结点前都会执行unlinkCancelledWaiters()检查尾结点是否取消,什么情况下结点会取消呢?是否只有尾结点被取消?

private void unlinkCancelledWaiters() {
	// 1. 获取条件队列头结点
    Node t = firstWaiter;
    Node trail = null;
   // 2. 当前结点不为空
    while (t != null) {
    	// 2.1 next 临时变量保存当前结点的下一个结点
        Node next = t.nextWaiter;
        // 2.2 若 t 状态不是 Node.CONDITION
        if (t.waitStatus != Node.CONDITION) {
        	// 2.2.1 断开 t 与后继结点的连接
            t.nextWaiter = null;
            // 2.2.2 trail = null 说明上一个结点为头结点且状态不为 Node.CONDITION,则更新头结点为 next
            if (trail == null)
                firstWaiter = next;
            // 2.2.3 trail != null 当前遍历的结点之前有未取消的结点,且当前结点已取消,则将略过当前结点,使前继结点与该结点的后继结点建立的连接
            else
                trail.nextWaiter = next;
            // 遍历到链表的结尾,则将 trail 设置为 尾结点
            if (next == null)
                lastWaiter = trail;
        }
        // 2.2 当前结点状态为 Node.CONDITION 则将其保存到 trail 中
        else
            trail = t;
        // 2.3 继续向后遍历链表结点,
        t = next;
    }
}

作业:unlinkCancelledWaiters() 的处理流程

问题:trail 的作用是什么?新的等待队列的lastWaiter

问题:条件队列中的结点非null吗?同步队列中的结点呢?

思考:node 什么情况下为 null,声明但未赋值?

① 为 null 说明为引用类型

② 声明但未赋值,只能是成员变量或类变量,私有变量需要在使用前赋值

class Person {
    private String name;
    public Person(){}
}
public class TestPerson{
    static Person p1;
    Person p2;
    
    public static void main(String[] args) throws ClassNotFoundException {
        Person p = new Person();
        System.out.println(p1 == null); // true
    }

    public void test1(){
        Person p3;
        System.out.println(p2 == null); // true
    }
}

③ 思考加入队列的结点的属性

同步队列:Node {prev, next, nextWaiter}

执行 addWaiter 时,Node node = new Node(Thread.currentThread(), mode);

等待队列:Node {firstWaiter, lastWaiter}

执行 addConditionWaiter 时 Node node = new Node(Thread.currentThread(), Node.CONDITION);

分析至此,暂时说明结点加入队列时是不为null,but 想起来 AQS 中好像有很多地方需要判断 null,需要针对这个点再捋一捋

借助 trail将等待队列转移到新的等待队列【过滤掉了 node.waitStatus != Node.CONDITION的结点】
在这里插入图片描述
跳出循环的条件 next == null,即遍历到原 lastWaiter.nextWaiter
在这里插入图片描述
上面的流程图还是不太好理解,从其中的属性 {firstWaiter, lastWaiter, t, next, trail} 入手分析

  1. 不会执行上述unlinkCancelledWaiters()的情况:lastWaiter == null说明队列为空,设置 firstWaiter = lastWaiter = node【从addConditionWaiter开始看】
  2. 排除情况1说明会执行whiletrial的作用:新队列的lastWaiter
    trial == null:遍历的t均处于取消状态
    trail == t:遍历的t未取消,则遍历第一个结点后,trail = firstWaiter
  3. 有取消的结点的情况
    trial == null:更新 firstWaiter = next
    trail == t:已借助trail形成新的等待队列,将添加到新队列的队尾
  4. 判断 next的状态,考虑 next == null时,否则继续进行2-4
    trial == nullfirstWaiter = next = nulllastWaiter = trail= null,即等待队列中仅有一个结点,该结点还取消了,此时并未建立新的等待队列
    trail == t:此时 trail.nextWaiter = next = null,设置lastWaiter = trail,并退出循环

上面的流程也说明:等待队列不存在 dummy head

执行为null的情况为:

① 队列为空:firstWaiter = lastWaiter = null

② 队列不为空:lastWaiter.nextWaiter = null

1.2 fullyRelease()

await() 方法通过 fullyRelease() 释放当前线程持有的同步资源

fullyRelease(node) -> release(arg) ->tryRelease(arg)【由子类实现】

此小节可以部分回答上面,addConditionWaiter()添加新结点前都会执行unlinkCancelledWaiters()检查尾结点是否取消?

final int fullyRelease(Node node) {
	// 1. 是否成功释放标识
    boolean failed = true;
    try {
    	// 2. 获取资源状态
        int savedState = getState();
        // 3. 释放资源成功
        if (release(savedState)) {
        	// 3.1 设置释放成功标识
            failed = false;
            // 3.2 返回资源状态
            return savedState;
        } else {
        	// 4. 当前线程不是持有锁的线程,则抛出 IllegalMonitorStateException 异常
            throw new IllegalMonitorStateException();
        }
    } finally {
    	// 5. 释放资源失败,则将结点的状态设置为取消
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

什么情况下node.waitStatus = Node.CANCELLED

fullyRelease(node)执行流程:

  1. finally 一定会执行,因此若 failed = true则会执行node.waitStatus = Node.CANCELLED
  2. failed = false的情况:成功执行release(arg)
  3. failed = true的情况:执行失败,抛出IllegalMonitorStateException异常

抛出IllegalMonitorStateException异常的原因?

1.2.1 release(arg)

public final boolean release(int arg) {
	// 1. 子类重写的 tryRelease(arg) 执行成功
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
        	// 1.2 唤醒头节点的后继节点
            unparkSuccessor(h);
        // 1.3 返回释放成功标识
        return true;
    }
    // 2. tryRelease(arg) 执行失败,返回释放失败标识
    return false;
}

1.2.2 tryRelease(arg)

ReentrantLock 重写的tryRelease(arg)为例

protected final boolean tryRelease(int releases) {
	// 1. 获取资源状态
    int c = getState() - releases;
    // 2. 判断当前线程是否是持有锁的线程
    if (Thread.currentThread() != getExclusiveOwnerThread())
    	//2.1 当前线程不是持有锁的线程则抛出 IllegalMonitorStateException 异常
        throw new IllegalMonitorStateException();
    // 3. 设置释放成功标识
    boolean free = false;
    // 4. 若state = 0已获取的资源全部释放
    if (c == 0) {
    	// 4.1 返回释放成功
        free = true;
        // 4.2 清除线程的独占状态
        setExclusiveOwnerThread(null);
    }
    // 5. 更新 state
    setState(c);
    // 6. 返回释放状态
    return free;
}

问题:这里抛出异常后,还会执行下面的 return free 吗?

测试用例:
① testThrow() 中抛出异常

② 函数调用流程:main()->testFullyRelease()->testRelease()->testThrow()

public class TestThrow {
    public static void main(String[] args) {
        testFullyRelease();
    }
    
    public static void testFullyRelease(){
        boolean failed = true;
        int state = -1;
        try{
            if(testRelease(state)){
                failed = false;
            }
        }finally{
            if(failed){
                System.out.println("IllegalMonitorStateException");
            }
        }
    }

    public static boolean testRelease(int arg){
        if(testThrow(arg)){
            return true;
        }
        return false;
    }

    public static boolean testThrow(int arg){
        if(arg < 0){
            throw new IllegalMonitorStateException();
        }
        boolean free = false;

        if(arg != -1){
            free = true;
        }
        return free;
    }
}

测试结果:

IllegalMonitorStateException
Exception in thread "main" java.lang.IllegalMonitorStateException
	at TEST.TestThrow.testThrow(TestThrow.java:36)
	at TEST.TestThrow.testRelease(TestThrow.java:28)
	at TEST.TestThrow.testFullyRelease(TestThrow.java:17)
	at TEST.TestThrow.main(TestThrow.java:10)

测试结论:

  1. 抛出异常,程序后面的语句不再执行,即若在 tryRelease(arg) 中抛出异常,则不会执行 return free
  2. 抛出异常方法的调用者 release(arg)中没有异常处理程序,会继续向上抛出异常,并且不会执行return free
  3. 若在 tryRelease(arg)抛出异常,fullyRelease(node)中抛出的IllegalMonitorStateException来自调用的函数tryRelease(arg),而非 else 分支
  4. fullyRelease(node)else分支抛出的异常可能是:持有资源的线程为完全释放资源

经过上面的分析后:节点取消的原因可能是:当前线程不是持有资源的线程,或者持有资源的线程未完全释放资源【有待考究】
即目前看到的,节点取消是发生在释放资源的过程中

那么取消的节点会在什么地方呢?同步队列还是等待队列

同步队列中是阻塞线程,线程进入同步队列有两种可能

① 未获取到同步资源,执行 Lock.park()

② 获取到同步资源,调用 await(),执行 signal() 或被中断,执行 enq(node) 加入同步队列

若是持有资源但未完全释放的线程,则可能执行了await()添加到等待队列中,但执行fullyRelease()失败,设置 node.waitStatus = Node.CANCELLED,执行unlinkCancelledWaiters()从等待队列中移除

而持有资源的线程会从同步队列中移除,因此对于持有资源但未完全释放的线程,可能是继续释放资源,而不在队列中等待或阻塞

若线程不是持有资源的线程,则不会执行 await()【?】,这一部分没有想清除

该疑点还与接下来的问题有关

为什么 执行 await()的时候不判断是持有当前线程的资源,而在释放资源的时候判断 ?

protected final boolean tryRelease(int releases) {
	// 1. 获取资源状态
    int c = getState() - releases;
    // 2. 判断当前线程是否是持有锁的线程
    if (Thread.currentThread() != getExclusiveOwnerThread())

这里,因为 tryRelease(arg)调用的是子类,所以在 await()中相当于没有直接判断释放资源的线程,可能需要从子类中找答案

注意到 getExclusiveOwnerThread()AbstractOwnableSynchronizer中的方法,AQS是其子类,但并没有重写该方法

1.3 isOnSyncQueue()

挂起线程之前,先用 isOnSyncQueue() 保证线程不在同步队列中

final boolean isOnSyncQueue(Node node) {
	// 1. node 状态为 Node.CONDITION 或 前驱节点为空,说明在条件队列中或者是还未加入同步队列中的结点
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    // 2. node 的 next 引用非空,则肯定在同步队列中
    if (node.next != null) // If has successor, it must be on queue
        return true;
    // 3. 将线程放入同步队列的操作 addWaiter() 添加node 到队尾,先将node.prev 指向 old node 再进行 cas 操作,若 cas 操作失败,则node.prev = old tail, node.next == null,因此需要进一步判断
    return findNodeFromTail(node);
}

如何判断节点是否在同步队列中?

节点可能的状态:不在队列中在运行态,在等待队列,在同步队列

等待队列:node.waitStatus == Node.CONDITION,由于等待队列的Node没有使用 prev,next,即若节点在等待队列中,node.prev == null

同步队列:节点的状态可能为:{ Node.SIGNAL, Node.PROPAGATE, 0}

节点可能正在队列中,node.next != null

或正在加入队列,执行addWaiter时,先建立与尾节点的连接,在执行CAS入尾,此时 node.prev != null

1.3.1 findNodeFromTail()

执行 findNodeFromTail() 是进一步判断是否是:结点加入同步队列,node.prev = old tail 但是 CAS 失败,出现 node.prev != null 但 node.next == null 的情况

private boolean findNodeFromTail(Node node) {
	// 1. 获取队尾结点 tail,仅当同步队列 tail 才非空
    Node t = tail;
    for (;;) {
    	// 1. 若 tail = node 则是同步队列
        if (t == node)
            return true;
        // 2. 若 tail = null 则是条件队列
        if (t == null)
            return false;
        t = t.prev;
    }
}

从队尾向前遍历,t != null 可以判断不是条件队列,但执行该步时,可能有线程又加入了队尾,

因此向前搜索待查找结点,此时该节点的CAS必然是已经成功的

2 signalAll()

ConditionObject 重写的await() 方法执行isOnSyncQueue(node)判断 node 不在同步队列中,node 封装的线程会被 LockSupport.park(this)挂起,可通过 signal/signalAll() 方法或中断唤醒被挂起的线程,因此本段代码若要继续向下分析,需要先将线程从条件队列中唤醒

while (!isOnSyncQueue(node)) {
	// 4.1 阻塞线程
    LockSupport.park(this);
    // 4.2 检查线程被唤醒的原因,若是中断被唤醒,则跳出 while 循环
    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
        break;
}

调用 signalAll() 方法的线程必须是已经持有资源的线程

// 将等待条件队列中的线程移到等待锁的队列中
public final void signalAll() {
	// 1. 若当前线程未持有资源(lock)则抛出 IllegalMonitorStateException 异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 2. 获取条件队列的头节点
    Node first = firstWaiter;
    // 3. 若条件队列头节点非空,则唤醒队列的头结点
    if (first != null)
        doSignalAll(first);
}

检查当前线程是否持有同步资源 isHeldExclusively由继承的子类实现

protected final Thread getExclusiveOwnerThread() {
	//1. exclusiveOwnerThread 保存了当前持有资源的线程
    return exclusiveOwnerThread;
}
protected final boolean isHeldExclusively() {
   	// 2. 获取当前线程并判断是否是持有资源的线程
    return getExclusiveOwnerThread() == Thread.currentThread();
}

2.1 doSignalAll()

若当前线程持有同步资源并且队列的头节点非空,则调用 doSignalAll() 方法唤醒所有等待的结点

private void doSignalAll(Node first) {
	// 1. 将队列的头尾结点置空
    lastWaiter = firstWaiter = null;
    do {
    	// 2.从条件队列的头节点开始依次将每个结点添加到同步队列中
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}

注意这里:使用 signal方式唤醒的时候,会将 node 移除等待队列first.nextWaiter = null;,而若通过中断的方式,直接执行enq(node)不会移除,这也是判断线程在等待队列中阻塞时是通过中断唤醒还是通过 signal唤醒的标志之一

2.1.1 transferForSignal(node)

transferForSignal(node) 将原条件队列中的结点添加到同步队列末尾

final boolean transferForSignal(Node node) {
    //1. 若 node.waiStatus != Node.CONDITION,说明结点已取消,返回添加失败
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
	// 2. CAS 置 node.waitStatus = 0 成功则利用 enq() 将该结点添加至同步队列末尾
    Node p = enq(node);
    // 3. node 添加至同步队尾成功,返回其前驱结点 p, 并获取 p.waitStatus
    int ws = p.waitStatus;
    // 4. 若前驱结点已取消或者 唤醒前驱结点失败,则直接唤醒
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

上述执行compareAndSetWaitStatus(node, Node.CONDITION, 0)失败,直接返回 false 的情况分析

执行 signalAll() 的线程是唤醒线程,唤醒的对象是等待队列中的节点,而等待队列中的节点有两种状态
{Node.CONDITION, Node.CANCELLED},因此节点若取消则不会在队列中,不用添加到同步队列【关于 Node.CANCELLED 理解还有点问题】

问题:CAS设置 waitStatus = Node.SIGNAL 时,为什么会直接唤醒线程?

思考:有无可能 有别的线程在修改 node.prev 的状态?node.prev = head ? node.prev.waitStatus = Node.PROPAGATE?

若不是第一种情况,则 node.prev 还未获取同步资源,所以不会唤醒 node,但是对于后面的两种情况,是可以唤醒的

作业:总结 signalAll() 唤醒条件队列中结点的流程
在这里插入图片描述

3 signal()

signal() 只会唤醒条件队列中的一个结点,即唤醒条件队列中第一个没有取消的结点

public final void signal() {
	// 1. 检查当前线程[调用该方法的线程]是否持有资源
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    // 2. 若条件队列头结点非空,则调用 doSignal(node) 唤醒结点
    if (first != null)
        doSignal(first);
}

3.1 doSignal(node)

doSignal(node) 唤醒第一个没有取消等待的结点

private void doSignal(Node first) {
    do {
    	// 1. 若头结点为空则条件队列中无结点,将条件队列的头尾结点置空
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
        // 2. 若结点被成功添加到同步队列中则跳出while循环
    } while (!transferForSignal(first) &&
    		// 2.1 会执行 && 后面的说明该结点已取消则继续向后遍历添加未取消的结点,直到队列为空
             (first = firstWaiter) != null);
}

signal() 只会唤醒队列中的一个节点,若唤醒成功则transferForSignal(first)返回true,退出while循环

若节点取消,则transferForSignal(first)返回 false,会唤醒未取消的节点

作业:signal() 和 signalAll() 的区别

4 等待线程从LockSupport.park(this)状态退出

LockSupport.park(this) 会阻塞当前线程,即线程会暂停在该处,线程继续执行有两种原因:

① 由其它线程通过 signal/signalAll() 唤醒

② 线程被中断,解除阻塞状态

public final void await() throws InterruptedException {
	// 1. 若当前线程在调用 await() 方法前已经被中断,直接抛出 InterruptedException() 异常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 2. 将当前线程封装成 Node 对象添加到 条件队列中
    Node node = addConditionWaiter();
    // 3. 释放当前线程所占用的锁,保存当前锁的状态
    int savedState = fullyRelease(node);
    // 
    int interruptMode = 0;
    // 4. 若当前队列不在同步队列中,说明刚执行await()方法,还未被 signal/signalAll 方法唤醒
    while (!isOnSyncQueue(node)) {
    	// 4.1 阻塞线程
        LockSupport.park(this);
        // 4.2 检查线程被唤醒的原因,若是中断被唤醒,则跳出 while 循环
        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);
}

执行await()方法的线程执行LockSupport.park(this)会进入阻塞状态,其它线程执行 signal/signalAlll 方法时会被唤醒,继续执行后面的流程

后续流程包括:

  1. interruptMode获取中断模式,判断线程在等待过程中是否发生中断
  2. acquireQueued(node, state)在同步队列中自旋-阻塞-自旋获取同步资源,线程获取资源成功后,该方法返回在同步队列中获取资源是否中断 return interrupted
  3. node.nextWaiter != null,则执行unlinkCancelledWaiters,对应中断唤醒的情况,直接执行的 ``enq(node)```并未将节点移除等待队列
  4. 若发生中断,则报告中断状态

问题:验证退出while循环时,节点已从等待队列转移到同步队列

4.1 interruptMode

await() 方法中使用 interruptMode 记录中断事件

// REINTERRUPT 退出 await() 方法时只需要再次中断线程,对应中断发生在 signal/signalAll 方法被调后
private static final int REINTERRUPT =  1;
// THROW_IE 退出 await() 方法时需要抛出 InterruptExceptin, 对应中断发生在 signal/signalAll 方法被执行之前
private static final int THROW_IE    = -1;

interruptMode = 0 代表这个过程中一直没有发生中断

4.2 checkInterruptWhileWaiting()

线程被唤醒后,首先使用 checkInterruptWhileWaiting() 检测中断

private int checkInterruptWhileWaiting(Node node) {
	// 1. 通过 Thred.interrupted() 判断线程是否中断,若未中断返回 0
	// 2. 若中断,则通过 transferAfterCancelledWait(node) 判断中断类型
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}

4.2.1 transferAfterCancelledWait(node)

将node结点移到同步队列中

返回两种中断对应的结果{THROW_IE , REINTERRUPT }

线程中断发生在被signal 唤醒前:transferAfterCancelledWait(node) = true

线程中断发生在被signal 唤醒后:transferAfterCancelledWait(node) = false

final boolean transferAfterCancelledWait(Node node) {
	// 1. node.waitStatus = Node.CONDITION 说明该结点改为被 signal/signalAll 唤醒过
	// 若线程被 signal/signalAll 唤醒,则 transferForSignal 中会首先设置 node.waitStatus = 0
   if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
   		// 1.1 中断发生在结点被 signal 唤醒前,则调用 enq(node) 将该结点添加到同步队列
       enq(node);
       // 1.2 返回true, 对应 interruptMode = THROW_IE 的情况
       return true;
   }
    // 2. CAS 失败说明线程已经被 signal/signalAll 唤醒, 等待线程进入同步
   while (!isOnSyncQueue(node))
       Thread.yield();
   return false;
}

问题:此处 yield() 的作用是?

① 执行 yield()说明CAS设置节点node.waitStatus = 0 失败,线程是被 signal/signalAll唤醒的

node 被 signal 唤醒时,会CAS更新 node.waitStatus = 0

final boolean transferForSignal(Node node) {
    //1. 若 node.waiStatus != Node.CONDITION,说明结点已取消,返回添加失败
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

node.waitStatus == Node.CONDITION线程的 waitStatus未更新,说明线程是在阻塞时被中断唤醒

③ 被中断唤醒会在transferAfterCancelledWait(node)中执行CAS设置 node.waitStatus = 0,CAS成功后会执行enq(node)将节点移至同步队列

此处也回答了上述问题,退出 while 循环时,节点一定进入了同步队列,因为通过 signal/signalAll 或者中断的方式都会将节点添加到同步队列

④ 若不是被中断唤醒,假设执行 signalAll 的线程为A,被唤醒的线程为B,则在A执行transferForSignal将唤醒的封装B的节点加入同步队列的同时,线程B若被中断则开始执行 if 条件中的checkInterruptWhileWaiting,可能会出现B还未加入同步队列中的情况。此时,需要使已被唤醒的线程B自旋,等待唤醒线程A将其添加到同步队列

流程图
在这里插入图片描述
上图中加入中断,原因是:若执行 yield(),说明线程必然被 signal唤醒,但出现判断是否中断时出现还未入尾的情况

final boolean transferForSignal(Node node) {
		....
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
    }

由于唤醒的流程是:先执行enq(node),再执行LockSupport.unpark(node.thread)

若无中断,不会出现,线程被唤醒但是还未入尾的情况

5 中断处理

等待线程从阻塞状态解除后,被添加到同步队列,通过checkInterruptWhileWaiting将中断模式保存在interruptMode

接下来的执行流程是:

  1. 在同步队列acquireQueued获取到资源
  2. 判断中断模式,执行reportInterruptAfterWait报告中断
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
    interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
    unlinkCancelledWaiters();
if (interruptMode != 0)
    reportInterruptAfterWait(interruptMode);

考虑可能发生中断的情况:

① 中断发生在 signal之前:在 LockSupport.park(thread)阶段中断,通过checkInterruptWhileWaiting添加到同步队列

② 中断发生在 signal之后:通过 transferForSignal(node)添加到同步队列,未执行获取同步资源前中断,对应上述分析 yield()的情况

③ 中断发生在同步队列中获取资源时:acquireQueued(node, state)返回中断状态

问题:会否被中断两次?即①+②,或① +③的情况

首先回顾下 checkInterruptWhileWaiting中检测到的中断的处理

private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}

transferAfterCancelledWait(node)= true :被中断唤醒,THROW_IE

transferAfterCancelledWait(node)= false:signal后被中断,REINTERRUPT

【会先执行添加到同步队列并获取资源,并不处理中断】

checkInterruptWhileWaiting不止保留了阻塞后唤醒的中断状态检查,还对中断标志位做了处理

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

isInterrupted(true)参数为true会清除中断标志位,也即在①或②中发生中断后,在③中也可能发生中断

下面逐条分析上述三条 if 语句执行的对应情况:

5.1 interruptMode = REINTERRUPT

对应上述③ + ② 或者 ③

③ 阻塞唤醒阶段未中断,在同步队列中获取同步资源时中断

② + ③ 在被signal唤醒后发生中断,同时在获取同步资源时又发生了中断

if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
    interruptMode = REINTERRUPT;

5.2 interruptMode = THROW_IE

对应 ① 或 ① + ③

此时判断是否在获取同步资源时中断没有意义,后续报告中断主要是根据interruptMode

if (node.nextWaiter != null) // clean up if cancelled
    unlinkCancelledWaiters();

为什么对应 ① 呢?

可以回顾上述分析 signal的流程,通过signal/signalAll唤醒,添加到同步队列前会设置node.nextWaiter != null
在这里插入图片描述
而通过中断解除阻塞状态时,节点也会从等待队列转移到同步队列,但是仅执行了enq(node)并没有从等待队列中移除
在这里插入图片描述
此时节点的状态是:node.waitStatus = 0,需要执行unlinkCancelledWaiters()从同步队列中移除

5.3 reportInterruptAfterWait(interruptMode)

private void reportInterruptAfterWait(int interruptMode) throws InterruptedException {
    //1. interruptMode = THROW_IE 则抛出异常 
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    // 2. interruptMode = REINTERRUPT 则再次中断一次
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();
}

interruptMode = THROW_IE则抛出异常

interruptMode = REINTERRUPT则再次中断一次

5.4 interruptMode = 0

这种情况下对应①,②,③都没有发生,因此不会执行reportInterruptAfterWait(interruptMode)

=_= ,有一种上瘾且吐血的感觉,继续加油,持续改进

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值