Java Condition源码分析

Condition是一个配合独占锁ReentrantLock实现类似Objectwaitnotify功能的等待队列。Objectwait、notify方法需要在synchronized代码块中执行,同样Conditionawait、signal需在lock获取独占锁成功之后调用。与notify只能唤醒锁对象上所有的阻塞线程相比,Condition可以唤醒指定的阻塞线程,更加灵活。

不管是notify还是signal,它们都是唤醒线程进行锁的竞争,所以被唤醒的线程不一定会获取锁成功,很有可能会竞争锁失败而重新进入阻塞状态。

案例

public class ConditionDemo {

    int queueSize = 5;
    PriorityQueue queue = new PriorityQueue(queueSize);

    private ReentrantLock lock = new ReentrantLock();
    Condition notFullCondition = lock.newCondition();
    Condition notEmptyCondition = lock.newCondition();

    //消费者
    class Producer implements Runnable{
        @Override
        public void run() {
            while (true){
                try {
                    lock.lock();
                    //消费者已经满了
                    if(queue.size() == queueSize){
                        System.out.println("消息队列已经满了");
                      	//生产者进入阻塞,线程添加到notFullCondition等待队列
                        notFullCondition.await();
                    }
                    double val = Math.random();
                    queue.offer(val);
                    System.out.println("生产一条消息"+val+"队列剩余大小"+(queueSize - queue.size()));
                    Thread.sleep(1000);
                    notEmptyCondition.signal();
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        }
    }

    class Consumer implements Runnable{

        @Override
        public void run() {
            while (true){
                try{
                    lock.lock();
                    if(queue.isEmpty()){
                        //队列已经空了
                        System.out.println("消息队列内的消息已经全部被消费完");
                      	//消费者进入阻塞,线程被添加到notEmptyCondition队列
                        notEmptyCondition.await();
                    }
                    Object val = queue.poll();
                    System.out.println("消费消息:"+val+",剩余"+queue.size()+"没有消费");
                    Thread.sleep(1000);
                    notFullCondition.signal();

                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        }
    }

    public static void main(String[] args) {
        ConditionDemo conditionDemo = new ConditionDemo();
        Thread producerThread = new Thread(conditionDemo.new Producer());
        Thread consumerThread = new Thread(conditionDemo.new Consumer());
        producerThread.start();
        consumerThread.start();
    }
}

执行结果:

ReentrantLock在非公平锁的情况下,生产者一直竞争到了锁,直到消息队列满了进入阻塞状态,消费者才竞争到了锁。如果将锁设置成公平锁,可以看到生产者和消费者轮流获取到锁。

生产一条消息0.5278959497722281队列剩余大小4
生产一条消息0.4028658058829394队列剩余大小3
生产一条消息0.630037443523446队列剩余大小2
生产一条消息0.1644722338258895队列剩余大小1
生产一条消息0.14783073976448924队列剩余大小0
消息队列已经满了
消费消息:0.14783073976448924,剩余4没有消费
消费消息:0.1644722338258895,剩余3没有消费
消费消息:0.4028658058829394,剩余2没有消费
消费消息:0.5278959497722281,剩余1没有消费
消费消息:0.630037443523446,剩余0没有消费
消息队列内的消息已经全部被消费完

源码分析

await()线程进入等待

在这里插入图片描述

如上图所示:线程调用await方法后,ReentrantLock阻塞队列中的线程节点将追加到Condition的阻塞队列的末尾。

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
  	//1.将当前线程保装成一个node对象,并加入到Condition阻塞队列
    Node node = addConditionWaiter();
  	//2.释放线程持有的所有的锁
    int savedState = fullyRelease(node);
    int interruptMode = 0;
  	//3.判断线程是否在aqs阻塞队列中
    while (!isOnSyncQueue(node)) {
      //如果没有就阻塞线程
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
  
  	//4.线程被唤醒后要重新添加入AQS等待队列
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
  	//过滤掉被取消的线程
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
  1. 将当前线程封装成一个Node对象,并加入到Condition阻塞队列的末尾。Condition调用await方法是在线程已经获取到锁的前提下,所以不存在并发问题。

    private Node addConditionWaiter() {
      	//Condition等待队列的尾节点
        Node t = lastWaiter;
      
        // 过滤掉被取消的线程
        if (t != null && t.waitStatus != Node.CONDITION) {
            unlinkCancelledWaiters();
            t = lastWaiter;
        }
      	//将线程节点追加到队列末尾
        Node node = new Node(Thread.currentThread(), Node.CONDITION);
        if (t == null)
            firstWaiter = node;
        else
            t.nextWaiter = node;
        lastWaiter = node;
        return node;
    }
    
    
    1. 释放线程持有的所有的锁。

    一个线程进入Condition阻塞队列,就必须将它获取的所有锁都释放掉。ReentrantLock是一个可重入锁,getState()可能是一个大于1的数字,所以调用release方法时,不能使用release(1),而是release(getState())

    ReentrantLock独占锁中,线程执行释放锁的操作就是将自己的节点的状态设置成0,然后唤醒后继的阻塞线程,后继的线程成功获取锁后,会将自己设置成aqs的头节点,最终达到了节点移除的操作。

    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
          	//获取锁的状态,savedState表示一个线程获取锁的全部次数
            int savedState = getState();
          	//释放锁,release(savedState)将所有的锁都释放掉
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }
    
    1. 判断线程是否在AQS阻塞队列中。

      Condition调用await方法后,线程会释放锁从AQS阻塞队列中移除并加入到Condition阻塞队列中,所以遍历AQS队列来判断是否要调用LockSupport.park(this);方法。

    final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        if (node.next != null) // If has successor, it must be on queue
            return true;
        //从尾部遍历aqs队列,新增的节点都是追加在队列的末尾,所以从后遍历效率更高。
        return findNodeFromTail(node);
    }
    
  2. 线程被唤醒后要重新添加入AQS等待队列,这个逻辑与ReentrantLock的加锁逻辑是一样的。参考(https://blog.csdn.net/YANYONG_/article/details/130630856)。

signal()唤醒操作

在这里插入图片描述

唤醒Condition阻塞队列中的阻塞线程时,线程执行signal方法时必须持有锁。

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // Condition等待队列的第一个线程节点
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

private void doSignal(Node first) {
	do {
		if ( (firstWaiter = first.nextWaiter) == null)
          lastWaiter = null;
          first.nextWaiter = null;
    //1.将Condition等待队列的第一个线程投递到AQS等待队列
	} while (!transferForSignal(first) &&(first = firstWaiter) != null);
}

  1. 将Condition等待队列的第一个线程投递到AQS等待队列。

    final boolean transferForSignal(Node node) {
        //重试设置 node 节点的状态
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
    
        //追加到AQS等待队列的末尾,并返回它的前置节点
        Node p = enq(node);
        int ws = p.waitStatus;
      	//如果前置节点的状态为取消或它的节点的状态设置为SIGNAL失败,直接唤醒待唤醒的节点;
        //如果前置节点状态设置成功,则当前节点等待它的前置节点来唤醒它。
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值