condition源码分析

目录:

参考博客:

https://www.cnblogs.com/gemine/p/9039012.html

https://www.jianshu.com/p/037c2bd99440

https://zhuanlan.zhihu.com/p/89703576

1:注意事项

condition是ReentrantLock中的对象,使用condition必须配合lock锁一起使用,否则会报错,原因以下会分析

2:创建方式

ReentrantLock lock = new ReentrantLock(); 

Condition fullCondition =lock.newCondition();

3:使用案例

package com.saytoyou.com.thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionTest {

    public static void main(String[] args) {

        ReentrantLock lock = new ReentrantLock();

        Condition condition = lock.newCondition();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("你好,我抢到了资源开始执行");
                try {
                    condition.await();
                    System.out.println("我又获取到资源了");
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }

            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("你好,我是线程signal");
                try {
                    condition.signal();
                    System.out.println("开始释放资源");
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }

            }
        }).start();
        
    }
}

说明:需要注意的是,signal必须是在await方法之前执行,不然会导致死锁,好了接下来进行源码分析

4:源码分析

4.1await源码如下

 public final void await() throws InterruptedException {  
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();  加入等待队列
            int savedState = fullyRelease(node);  释放当前线程占用的锁资源,因为线程不知道state是几,所以释放完成,返回state的值,保存起来
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {  循环判断是否在同步队列中,在同步队列中,说明signal调用了,可以进行如下操作,否否则等待
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) 检查是否被中断过,返回中断的状态,同时加入等待队列
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE) 利用AQS放入阻塞队列,尝试获取资源,设置state值再利用
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled    
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode); 配合checkInterruptWhileWaiting方法返回值判断是要抛出异常还是自我中断
} 

whille()循环详解,刚开始进来肯定不在同步对列中,所以false进入循环,park,等待唤醒,唤醒之后不管下面if循环是否有效,再次进入while循环,这时候


肯定已经在同步队列了,所以会跳出循环,进行接下来的操作

注意最后一步checkInterruptWhileWaiting方法分析如下,判断是否需要延续中断,最后会有解释

结合上面判断中断状态就可以理解了,如果是在signal之前发生的中断,则需要补充一次中断,否则抛出异常,因为不是通过signal唤醒的,而是中断唤醒的

4.2:上述addConditionWaiter源码如下

 private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();  清除队列中不是condition状态的节点
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }
新建一个node节点,赋值为-2也就是condition状态,然后加入到队列尾部,同时把最后一个lastWaiter指向新节点

unlinkCancelledWaiters,方法就是把队列中不是condition状态的节点清楚掉

4.3:unlinkCancelledWaiters方法如下

 private void unlinkCancelledWaiters() {
            Node t = firstWaiter;
            Node trail = null;
            while (t != null) {
                Node next = t.nextWaiter;
                if (t.waitStatus != Node.CONDITION) {
                    t.nextWaiter = null;  帮助gc
                    if (trail == null)
                        firstWaiter = next;
                    else
                        trail.nextWaiter = next;  如果发现状态不正确,就把上个节点剔除,保存当前节点
                    if (next == null)
                        lastWaiter = trail;
                }
                else
                    trail = t;  存放上一个节点
                t = next; 存放当前节点,循环往上
            }
        }
大概就是trail保存上一个节点,t保存当前节点,循环往下,如果t节点的下个节点状态不正确,就把t剔除,直接用下一个节点关联到上一个节点
4.4:fullyRelease源码如下:
回到addwaiter方法中
fullyRelease(node);该方法的主要作用就是释放调用await方法的线程的锁资源,同时临时返回state的值,供后面使用
final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState(); 
            if (release(savedState)) {  在AQS中已经知道,释放资源
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

4.5:checkInterruptWhileWaiting源码分析如下:

// 退出await的时候重新设置中断
 private static final int REINTERRUPT =  1;

// await退出的时候需要抛出异常
 private static final int THROW_IE    = -1;

 0 说明await期间没有发生中断
// 判断是否在线程挂起期间发生了中断,如果发生了中断,是 signal 调用之前中断的:抛出异常;还是 signal 之后发生的中断:重新设置中断
private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
}

// 线程处于中断状态,才会调用此方法
final boolean transferAfterCancelledWait(Node node) {
        // 如果此时signal已经发生,NODE状态就不再是CONDITION,所以CAS成功的话,说明中断是在signal前发生的
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            // 即使signal还没发生,此时是中断引起的unpark,依然会由于条件队列迁移到阻塞队列尾部
            enq(node);
            return true;
        }

        // 执行到这一步,说明上边CAS失败,说明waitstatus已不为CONDITION,即signal已经发生后才发生的中断
        // signal 方法会将节点转移到阻塞队列,但是可能还没完成,这边自旋等待其完成
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
}


5:signal方法分析

private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)  如果下一个队列为空,说明最后一个,清空队列
                    lastWaiter = null;  清空队列
                first.nextWaiter = null; 帮助gc
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }



final boolean transferForSignal(Node node) {
       
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))  如果状态不是condition,则进行下一个,这一个节点直接丢弃
            return false;
        Node p = enq(node); 存放阻塞队列
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) 更新当前节点状态为signal
            LockSupport.unpark(node.thread);  唤醒当前节点 ,注意一般代码走不到这里,真正的唤醒,还是最后的release方法,所以在finally里面必须调用release进行释放
        return true;
    }

结合上面判断中断状态就可以理解了,如果是在signal之前发生的中断,则需要补充一次中断,否则抛出异常,因为不是通过signal唤醒的,而是中断唤醒的

 

posted @ 2022-04-11 15:55  xzlnuli  阅读(57)  评论(0编辑  收藏  举报