JUC之Condition实现原理

Condition是一个多线程协调通信的工具类,
Condition是对线程进行控制管理的接口,具体实现是AQS的一个内部类ConditionObject,可以让某些线程一起等待某个条件(condition),只有满足条件时,线程才会被唤醒
Condition是一个接口,这些是抽象方法Condition是一个接口,这些是抽象方法

我们本篇文章主要看的就是await()和signal()
首先了解一下AQS同步队列与等待队列数据结构
等待队列是一个FIFO单向链表

在这里插入图片描述
在这里插入图片描述
同步队列是一个FIFO双向链表

在这里插入图片描述
在这里插入图片描述
下面来看一个生产者消费者对Condition的应用场景
生产者

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();
        }
    }
}

消费者

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();
        }
    }
}

测试类,需要注意,生产者消费者需要互斥,共同竞争同一把锁,且Condition也要是同一个对象

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方法并阻塞,然后执行生产者,当生产者生产满了之后,进入wait方法,唤醒消费者。依次往复,下面跟踪源码看逻辑

public final void await() throws InterruptedException {
    if (Thread.interrupted())//1. 如果中断线程直接抛异常
        throw new InterruptedException();
    //2. 将当前线程添加到JUC等待队列,如果队列不存在,将当前线程封装成Node节点 firstWaiter、lastWaiter均指向这个节点
    Node node = addConditionWaiter();
    //3. 释放锁,返回当前锁持有JUC state次数
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //4.如果一个节点(始终是最初放置在条件队列上的节点)现在正等待在同步队列上重新获取,则返回true。           
    while (!isOnSyncQueue(node)) {
        //5. 阻塞线程
        LockSupport.park(this);
    //6. 等待时检查中断。
    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //被唤醒后,尝试拿到锁,并增加线程中断判断,如果线程被中断,也会走到这里,并进入AQS阻塞方法,判断是否被中断过,如果如果中断过直接中断线程
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) //  清理掉不为-2的线程
        unlinkCancelledWaiters();
    if (interruptMode != 0)//如果中断标识不为空,进行中断处理
        reportInterruptAfterWait(interruptMode);
}

第一步不需要看了,直接看第二步吧

private Node addConditionWaiter() {
	//拿到最后一个节点
     Node t = lastWaiter;
     // 如果最后一个节点被取消了,就清理。            
     if (t != null && t.waitStatus != Node.CONDITION) {
         unlinkCancelledWaiters();
         t = lastWaiter;
     }
     //创建一个新的Node节点
     Node node = new Node(Thread.currentThread(), Node.CONDITION);
     //如果最后一个节点是空的,说明整个链表都是空的,就将首尾都指向新建的Node
     if (t == null)
         firstWaiter = node;
     else
         t.nextWaiter = node;
     lastWaiter = node;
     //返回新建的Node节点
     return node;
 }
  1. 释放锁,返回释放次数
final int fullyRelease(Node node) {
   boolean failed = true;
    try {
    	//获取当前线程的持有锁的state次数
        int savedState = getState();
        //有几次都直接释放掉,并返回释放次数
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
    	//释放失败的话,说明线程有问题,直接标记cancel
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}
public final boolean release(int arg) {
	//尝试释放锁,释放成功的话返回true,执行锁唤醒逻辑,
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
private void unparkSuccessor(Node node) {
//清除等待状态,将同步队列head节点的状态修改为0
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    //获取线程阻塞,说明当前线程就是同步队列的head,先获取下一个节点,如果下一个节点不为空且为正常状态,就唤醒第二个节点
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}
final boolean isOnSyncQueue(Node node) {
		//判断当前状态是新建Node的状态,或者当前Node在AQS同步队列里,判断是否在同步队列里,就看这个节点是否有前一个节点,如果有,说明在队列中。
  if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    if (node.next != null) // If has successor, it must be on queue
        return true;
    
     //如果前两步判断不出,就遍历判断,从尾部向前遍历
    return findNodeFromTail(node);
}
  1. 将线程挂起,现在现在就在等待队列中阻塞了
    现在第一个线程阻塞了。那么现在看第二个线程。第二个线程需要调用signal()方法来进行一些操作
public final void signal() {
	//如果操作的不是当前拿到锁的线程,抛出异常
   if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    //头节点不为空,就执行
    if (first != null)
        doSignal(first);
}
//这里主要的逻辑是将等待队列的firstWaiter移动到同步队列的尾部,然后将firstWaiter修改为下一个等待队列的节点,如果没有了就变成null。判断移动到同步队列的节点状态,如果为cancel或修改状态为SIGNAL失败,唤醒以重新同步

private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
 }

然后ThreadB调用awit()方法,唤醒同步队列的ThreadA,消费者需要从阻塞的位置退出继续执行

ThreadB进入等待队列,一直重复

而对于Condition的signalAll()方法,相当于对等待队列中的每个节点均执行一次signal()方法,效果就是将等待队列中所有节点全部移动到同步队列中,并唤醒每个节点的线程。

等待队列不像同步队列的中断一样,中断后标记中断标识,等Node拿到锁后才响应中断。等待队列会立即响应,将需要中断的Node放入同步队列,并将状态修改为0

Condition的使用场景:JUC包中提供了很多BlockQueue,可以直接使用

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值