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已经被加入到同步队列中了

原创文章 25 获赞 22 访问量 1243

猜你喜欢

转载自blog.csdn.net/a1240466196/article/details/105497447