condition实现解析 1.8

任意一个java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long
timeOut)、notify()、notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式.condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这俩者在使用方法以及功能特性上还是有差别的

以上摘自《java 并发编程的艺术》5.6节
在这里插入图片描述

condition相对于Object监视器方法最大的优势在于, condition能设置多个等待队列,这意味着利用condition能够设计出更加灵活、性能更好的数据结构
例如:阻塞队列ArrayBlockingQueue

	//基于lock接口的显示锁
    final ReentrantLock lock;

    //针对消费者的等待队列
    private final Condition notEmpty;

    //针对生产者的等待队列
    private final Condition notFull;

	public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        //重点看这里,通过同一个监视器锁创建出俩个不同的等待队列
        //这样可以根据不同的条件判断,来精准的使用具体的等待队列,避免不必要的线程等待唤醒操作
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }

	//看下具体的使用,生产者生产对象
	public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
        	//重点看这里,如果判断队列已满,那么通知该生产者线程等待
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }
	
	private void enqueue(E x) {
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        //重点看这里,精准的唤醒一个消费者线程,上面的生产者线程不受影响
        //如果使用notify()的话,并不知道唤醒的是生产者线程还是消费者线程
        //如果使用notifyAll()唤醒所有线程,又会造成大量的锁竞争,资源浪费
        notEmpty.signal();
    }

接下来进入正题,看下condition是如何实现的,先来看下condition接口定义了哪些方法

//相当于Object.wait(),设置当前线程处于等待状态,响应中断,抛出InterruptedException
void await() throws InterruptedException;

//await 忽略中断
void awaitUninterruptibly();

//await 加上超时机制,响应中断,返回 nanosTimeout-消耗时间,如果返回<=0,说明已超时
long awaitNanos(long nanosTimeout) throws InterruptedException;

//await 加上超时机制,并且可以指定时间单位,响应中断
boolean await(long time, TimeUnit unit) throws InterruptedException;

//await 指定时间界限,在此时间前被通知唤醒则返回true,否则为false,响应中断
boolean awaitUntil(Date deadline) throws InterruptedException;

//唤醒一个等待线程,前提是正在执行该方法的线程已经获取了condtion相对应的锁
void signal();

//唤醒所有等待线程
void signalAll();

AQS的内部类ConditionObject实现了condition接口

//condtion 和同步锁队列是共用同一个node对象
//condtion的等待队列中存放了所有调用该condition.await 方法的线程
//当调用condtion.signal 方法,等待队列中的节点将会挪到同步锁队列中
public class ConditionObject implements Condition, java.io.Serializable {
        //首节点
        private transient Node firstWaiter;
        //尾节点
        private transient Node lastWaiter;
}

//在看下node中 condtion特有的参数
static final class Node {
	//针对于condtion节点,初始状态为CONDITION
    static final int CONDITION = -2;
	
	//和同步器共用的一个参数,在同步器中代表node的模式(独占、共享)
	//condtion中表示指向下一个节点,condtion是一个单向链表
	volatile int waitStatus;
}

下面通过图示来表达同步锁队列和 等待队列之间的关联

在这里插入图片描述

看具体的源码await

		//主要逻辑
        //1、在condition队列中添加一个节点,执行Release释放自身同步状态
        //2、循环判断该节点是否被转移到锁同步队列,如果没有则挂起等待
        //3、如果被中断,则跳出2的循环做出相应响应(这个响应要在重新获取同步状态后才能做)
        //第一个过程是在获取同步状态的情况下执行,所以不用考虑并发情况,
        //后俩个过程因为已经释放同步状态了,需要考虑并发
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            //在condition队列中添加一个节点,状态是condition
            Node node = addConditionWaiter();
            //执行release,如果失败了,抛出IllegalMonitorStateException
            //如果这里成功,则表示该线程已经成功释放了同步状态,下面的流程都需要考虑并发情况了
            int savedState = fullyRelease(node);

            //这里初始状态为0
            //1、当该节点被移到同步锁队列之前中断(signal之前),则interruptMode为THROW_IE,最终会抛出InterruptedException
            //2、当该节点在移动同步锁队列之后中断(signal之后),则interruptMode为REINTERRUPT,保留interrupt状态
            //3、如果没有被中断,保持原有状态0
            int interruptMode = 0;
            //循环判断该节点是否被移动到同步锁队列,如果没有则挂起等待
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                //如果是被中断了,则将节点转移到同步锁队列,同时跳出循环
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //进行到这里说明节点已经被转移到同步锁队列了,执行acquireQueued
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            //执行到这里,说明acquire成功,已经重新获取了同步状态
            if (node.nextWaiter != null) // clean up if cancelled
                //清理condtion等待队列中的CANCELLED节点
                unlinkCancelledWaiters();
            //如果不为0,说明之前该线程中断过,那么做出相应的响应
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
		

	//判断该节点在同步锁队列中是否存在
	final boolean isOnSyncQueue(Node node) {
		//当状态为CONDITION时,或者当pre(同步器队列用到的前驱)为null,则说明不存在
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        //next是同步器用到的后继节点,如果不为空,那么该节点就肯定在同步锁队列了
        //这里很有趣,下面会具体分析为什么没有node.prev!=null
        if (node.next != null) 
            return true;
        //从同步锁队列尾部查找是否有这个节点    
        return findNodeFromTail(node);
    }

	

isOnSyncQueue方法有个很有趣的地方,if (node.next != null) 可以判断节点肯定已经在同步锁队列了,那么同理node.prev != null 是不是也能证明呢
答案当然不是了,看以下代码

//这个方法是aqs往同步锁队列添加节点
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //如果未初始化队列,创建head,cas赋值
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
             //重点在这里,是先把该节点的先驱和尾节点关联上,再使用cas
        	//如果这个过程中cas失效了,那么该节点就添加失败了,那这个关联当然也就是无效的
        	//而signal时会调用这个方法,所以不能通过node的pre是否为null来判断节点是否已经成功移到同步锁队列
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

其中还有一个比较重要的方法是checkInterruptWhileWaiting,判断线程有没有收到中断讯号、中断的时机(signal前还是signal后)

	private int checkInterruptWhileWaiting(Node node) {
            //当检测到线程中断信号则执行transferAfterCancelledWait,否则返回0
            //transferAfterCancelledWait逻辑主要判断中断的时机
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }
        
        
   final boolean transferAfterCancelledWait(Node node) {
        //如果cas修改状态成功了,说明此时还没有收到signal信号
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            //将该节点转移到同步锁队列
            //需要注意的是,虽然执行到这里注定了该线程会被抛出InterruptedException
            //但是也要等到获取同步状态之后才能抛出,否则会产生线程安全问题
            enq(node);
            return true;
        }
        //上面cas失败说明已经收到signal信号,singnal方法已经执行
        //但是也有可能没执行完,也就是节点还正在转移过程中,所以进行自旋等待
        //返回false表明该线程只会记录中断状态,不会抛错
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }

到这里await基本讲完了,awaitUninterruptibly、awaitNanos原理基本一样就不多描述了,接下来看看signal

	public final void signal() {
			//这里需要注意,isHeldExclusively是一个空方法,需要用户自己判断正在执行的线程是否已经获得同步
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
    }

	//需用用户继承重写,一般的逻辑就是判断当前线程是否获得同步状态,
	//这样接下来的操作就是线程安全的,否则会带来线程安全隐患
	protected boolean isHeldExclusively() {
        throw new UnsupportedOperationException();
    }


	
	private void doSignal(Node first) {
            do {
            	//头节点出队,重新设置nextWaiter为头节点
                if ( (firstWaiter = first.nextWaiter) == null)
                	//如果队列为空,设置lastWaiter为null
                    lastWaiter = null;
                //原头节点断开后继关联
                first.nextWaiter = null;
        	//transferForSignal失败表示该节点signal失败,继续下一个节点
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
     }


	final boolean transferForSignal(Node node) {
        //如果这里cas失败,表示这个节点状态是cancel,那么直接返回找下一个节点
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        //将该节点放入同步锁队列
        //注意,这里返回的p是node的前驱节点
        Node p = enq(node);
        int ws = p.waitStatus;
        //如果前驱节点是cancel,或者设置SIGNAL失败,则唤醒本节点处理
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值