J.U.C 学习(二)之 “Condition 条件控制”

在学习 synchronized 的时候,有学过 wait/notify 的基本使用,结合 synchronized 可以实现对线程的通信。那么既然 J.U.C 里面提供了锁的实现机制,那 J.U.C 里面有没有提供类似的线程通信的工具呢? 于是找啊找,发现了一个 Condition 工具类。

Condition 是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件 (condition),只有满足条件时,线程才会被唤醒
这篇文章写的不错https://blog.csdn.net/zx48822821/article/details/86768484

同步队列和等待队列

首先我们需要了解同步队列和等待队列的概念。简单的理解是同步队列存放着竞争同步资源的线程的引用(不是存放线程),而等待队列存放着待唤醒的线程的引用。

Condition使用示例

插播一道编程题
三个线程循环打印abc

package com.xhc.test.thread.mianshi;

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

/**
 * @author xuehuichen
 * @version V1.0
 * @Package com.xhc.test.thread.mianshi
 * @date 2021/3/9 下午7:21
 * @Copyright © 2016-2017 同程旅行
 */
public class ThirdThreadPrintABC {

        private int count = 0;
        private Lock lock = new ReentrantLock();
        private Condition conditionA = lock.newCondition();
        private Condition conditionB = lock.newCondition();
        private Condition conditionC = lock.newCondition();

        public static void main(String[] args) {
            ThirdThreadPrintABC printABC = new ThirdThreadPrintABC();
            ThreadA threadA = printABC.new ThreadA();
            ThreadB threadB = printABC.new ThreadB();
            ThreadC threadC = printABC.new ThreadC();
            threadA.start();
            threadB.start();
            threadC.start();
        }

        /**
         * 打印A
         * @author LKB
         *
         */
        class ThreadA extends Thread{
            public void run(){
                try {
                    lock.lock();
                    while(count < 30){
                        if (count%3 != 0) {
                            conditionA.await();
                        }
                        System.out.println("A");
                        count ++;
                        conditionB.signalAll();
                    }
                } catch (Exception e) {
                    // TODO: handle exception
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                    System.out.println("ThreadA 中执行了 unlock");
                }
            }
        }

        /**
         * 打印B
         * @author LKB
         *
         */
        class ThreadB extends Thread{
            public void run(){
                try {
                    lock.lock();
                    while(count < 30){
                        if (count%3 != 1) {
                            conditionB.await();
                        }
                        System.out.println("B");
                        count ++;
                        conditionC.signalAll();
                    }
                } catch (Exception e) {
                    // TODO: handle exception
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                    System.out.println("ThreadB 中执行了 unlock");
                }
            }
        }

        /**
         * 打印C
         * @author LKB
         *
         */
        class ThreadC extends Thread{

            public void run(){
                try {
                    lock.lock();
                    while(count < 30){
                        if (count%3 != 2) {
                            conditionC.await();
                        }
                        System.out.println("C");
                        System.out.println("+++++++");
                        System.out.println("-------");
                        count ++;
                        if(count < 30){
                            conditionA.signalAll();
                        }
                    }
                } catch (Exception e) {
                    // TODO: handle exception
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                    System.out.println("ThreadC 中执行了 unlock");
                }
            }
        }

}

我们还是创建一个生产者消费者的模型

生产者Producer

public class Producer implements Runnable{

    private Queue<String> msg;
    private int maxSize;
    Lock lock;
    Condition condition;

    public Producer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
        this.msg = msg;
        this.maxSize = maxSize;
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        int i=0;
        while(true){
            i++;
            lock.lock();
                while(msg.size()==maxSize){
                    System.out.println("生产者队列满了,先等待");
                    try {
                        condition.await(); //阻塞线程并释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("生产消息:"+i);
                msg.add("生产者的消息内容"+i);
                condition.signal(); //唤醒阻塞状态下的线程
            lock.unlock();
        }
    }
}

消费者 Consumer

public class Consumer implements Runnable{

    private Queue<String> msg;
    private int maxSize;
    Lock lock;
    Condition condition;

    public Consumer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
        this.msg = msg;
        this.maxSize = maxSize;
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        int i=0;
        while(true){
            i++;
            lock.lock(); //synchronized
            while(msg.isEmpty()){
                System.out.println("消费者队列空了,先等待");
                try {
                    condition.await(); //阻塞线程并释放锁   wait
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消费消息:"+msg.remove());
            condition.signal(); //唤醒阻塞状态下的线程
            lock.unlock();
        }
    }
}

main方法

public class App 
{
    public static void main( String[] args )
    {
        Queue<String> queue=new LinkedList<>();
        Lock lock=new ReentrantLock(); //重入锁
        Condition condition=lock.newCondition();
        int maxSize=5;

        Producer producer=new Producer(queue,maxSize,lock,condition);
        Consumer consumer=new Consumer(queue,maxSize,lock,condition);

        Thread t1=new Thread(producer);
        Thread t2=new Thread(consumer);
        t1.start();
        t2.start();

    }
}

通过这个案例简单实现了 wait 和 notify 的功能,当调用 await 方法后,当前线程会释放锁并等待,而其他线程调用 condition 对象的 signal 或者 signalall 方法通知被阻塞的线程,然后自己执行 unlock 释放锁,被唤醒的线程获得之前的锁继续执行,最后释放锁。所以,condition 中两个最重要的方法:

  • await:把当前线程阻塞挂起
  • signal:唤醒阻塞的线程
Condition 源码分析

调用 Condition,需要获得 Lock 锁,所以意味着会存在一个 AQS 同步队列,先来看 Condition.await 方法

condition.await
调用 Condition 的 await()方法(或者以 await 开头的方法),会使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。当从 await()方法返回时,当前线程一定获取了 Condition 相关联的锁

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();//创建一个新的节点,节点状态为condition,采用的数据结构仍然是链表
    int savedState = fullyRelease(node);//释放当前的锁,得到锁的状态,并唤醒AQS队列中的一个线程
    int interruptMode = 0;
    //如果当前节点没有在同步队列上,即还没有被 signal,则将当前线程阻塞
    while (!isOnSyncQueue(node)) {//判断这个节点是否在 AQS 队列上,第一次判断的是 false,因为前面已 经释放锁了
        LockSupport.park(this);//第一次总是 park 自己,开始阻塞等待
        // 线程判断自己在等待过程中是否被中断了,如果没有中断,则再次循环,会在 isOnSyncQueue 中判断自己是否在队列上. 
        // isOnSyncQueue 判断当前 node 状态,如果是 CONDITION 状态,或者不在队列上了,就继续阻塞.
        // isOnSyncQueue 判断当前 node 还在队列上且不是 CONDITION 状态了,就结束循环和阻塞.
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 当这个线程醒来,会尝试拿锁, 当 acquireQueued 返回 false 就是拿到锁了.
	// interruptMode != THROW_IE -> 表示这个线程没有成功将 node 入队,但 signal 执行了 enq方法让其入队了.
	// 将这个变量设置成 REINTERRUPT.
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    // 如果 node 的下一个等待者不是 null, 则进行清理,清理 Condition 队列上的节点.
	// 如果是 null ,就没有什么好清理的了.
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    // 如果线程被中断了,需要抛出异常.或者什么都不做
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

fullyRelease
fullRelease,就是彻底的释放锁,什么叫彻底呢,就是如果当前锁存在多次重入,那么在这个方法中只需要释放一次就会把所有的重入次数归零。

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        //获得重入的次数
        if (release(savedState)) {
        	//释放锁并且唤醒下一个同步队列中的线程
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

isOnSyncQueue
判断当前节点是否在同步队列中,返回 false 表示不在,返回 true 表示在
如果不在 AQS 同步队列,说明当前节点没有唤醒去争抢同步锁,所以需要把当前线程阻塞起来,直到其他的线程调用 signal 唤醒
如果在 AQS 同步队列,意味着它需要去竞争同步锁去获得执行程序执行权限
为什么要做这个判断呢?原因是在 condition 队列中的节点会重新加入到 AQS 队列去竞争锁。也就是当调用 signal 的时候,会把当前节点从 condition 队列转移到 AQS 队列

判断线程是否在AQS队列中的方法:

  • 如果 ThreadA 的 waitStatus 的状态为 CONDITION,说明它存在于 condition 队列中,不在 AQS 队列。因为AQS 队列的状态一定不可能有 CONDITION
  • 如果 node.prev 为空,说明也不存在于 AQS 队列,原因是 prev=null 在 AQS 队列中只有一种可能性,就是它是head 节点,head 节点意味着它是获得锁的节点。
  • findNodeFromTail,表示从 tail 节点往前扫描 AQS 队列,一旦发现 AQS 队列的节点和当前节点相等,说明节点一定存在于 AQS 队列中

Condition.signal
await 方法会阻塞 ThreadA,然后 ThreadB 抢占到了锁获得了执行权限,这个时候在 ThreadB 中调用了 Condition 的 signal()方法,将会唤醒在等待队列中节点,会将节点移到同步队列中

public final void signal() {
    if (!isHeldExclusively()) // 先判断当前线程是否获得了锁,这个判断比较简单,直接用获得锁的线程和当前线程相比即可
        throw new IllegalMonitorStateException();
    Node first = firstWaiter; // 拿到 Condition 队列上第一个节点
    if (first != null)
        doSignal(first);
}

Condition.doSignal
对 condition 队列中从首部开始的第一个 condition 状态的节点,执行 transferForSignal 操作,将 node 从 condition 队列中转换到 AQS 队列中,同时修改 AQS 队列中原先尾节点的状态

private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null) // 如果第一个 节点的下一个节点是 null, 那么, 最后一个节点也是 null.
            lastWaiter = null; // 将 next 节点设置成 null
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

AQS.transferForSignal
该方法先是 CAS 修改了节点状态,如果成功,就将这个节点放到 AQS 队列中, 然后唤醒这个节点上的线程。此时,那个节点就会在 await 方法中苏醒

final boolean transferForSignal(Node node) {
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))//更新节点的状态为0,如果更新失败,只有一种可能就是节点被 CANCELLED 了
            return false;

        Node p = enq(node);//调用enq,把当前节点添加到AQS队列。并且返回当前节点的上一个节点,也就是原 tail 节点
        int ws = p.waitStatus;
        // 如果上一个节点的状态被取消了, 或者尝试设置上一个节点的状态为 SIGNAL失败了(SIGNAL 表示: 他的 next 节点需要停止阻塞)
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread); // 唤醒节点上的线程
        return true;
    }
Condition 总结

阻塞:await() 方法中,在线程释放锁资源之后,如果节点不在 AQS 等待队 列,则阻塞当前线程,如果在等待队列,则自旋等待尝试获取锁
释放:signal() 后,节点会从 condition 队列移动到 AQS 等待队列,则进入 正常锁的获取流程

Condition流程图:

在这里插入图片描述
下一篇
J.U.C 学习(三)之 “阻塞队列”

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值