JUC中Condition分析

一、概述

Condition将Object监控器方法( wait , notify和notifyAll )分解为不同的对象,通过将它们与任意Lock实现结合使用,可以使每个对象具有多个等待集。 当“ Lock替换了synchronized方法和语句的使用时,“ Condition替换了“对象”监视器方法的使用。

Conditions条件(也称为条件队列或条件变量)提供了一种手段,为一个线程暂停执行(“等待”)直到另一线程通知某些状态条件现在为真。 由于对该共享状态信息的访问发生在不同的线程中,因此必须对其进行保护,因此某种形式的锁与该条件相关联。 等待条件提供的关键属性是它自动释放关联的锁并挂起当前线程,就像Object.wait一样。

Condition实例从本质上绑定到一把锁。 要获取特定Lock实例的Condition实例,请使用其newCondition()方法。

例如,假设我们有一个有界缓冲区,它支持put和take方法。 如果尝试在空缓冲区上进行take ,则线程将阻塞,直到有可用项为止。 如果尝试在完整的缓冲区上进行put ,则线程将阻塞,直到有可用空间为止。 我们希望保留等待put线程和take线程在分离(不同)的等待集中,以便我们可以使用仅当缓冲区中的项目或空间可用时才通知单个线程的优化。 这可以使用两个Condition实例来实现。( java.util.concurrent.ArrayBlockingQueue类提供了此功能,因此没有理由实现此示例用法类。)


   class BoundedBuffer {
     final Lock lock = new ReentrantLock();
     final Condition notFull  = lock.newCondition(); 
     final Condition notEmpty = lock.newCondition(); 
  
     final Object[] items = new Object[100];
     int putptr, takeptr, count;
  
     public void put(Object x) throws InterruptedException {
       lock.lock();
       try {
         while (count == items.length)
           notFull.await();
         items[putptr] = x;
         if (++putptr == items.length) putptr = 0;
         ++count;
         notEmpty.signal();
       } finally {
         lock.unlock();
       }
     }
  
     public Object take() throws InterruptedException {
       lock.lock();
       try {
         while (count == 0)
           notEmpty.await();
         Object x = items[takeptr];
         if (++takeptr == items.length) takeptr = 0;
         --count;
         notFull.signal();
         return x;
       } finally {
         lock.unlock();
       }
     }
   }

Condition实现可以提供与Object监视方法不同的行为和语义,例如保证通知的顺序,或者在执行通知时不需要保持锁定。 如果实现提供了这种特殊的语义,则实现必须记录这些语义。

请注意, Condition实例只是普通对象,它们本身可以用作synchronized语句中的目标(synchronized(condition)),并且可以调用自己的监视器wait和notification方法。 获取Condition实例的监视器锁或使用其监视方法与获取与该Condition相关联的Lock或使用其await和signal方法没有特定的关系。 建议避免混淆,除非可能在它们自己的实现中,否则不要以这种方式使用Condition实例。除非另有说明,否则为任何参数传递null值都会导致引发NullPointerException 。

实现注意事项

当等待Condition ,通常允许进行“虚假唤醒”,作为对底层平台语义的让步。 这对大多数应用程序几乎没有实际影响,因为应该始终在循环中等待Condition ,以测试正在等待的状态判断情况。 一个实现可以自由地消除虚假唤醒的可能性,但是建议应用程序程序员始终假定它们会发生,因此总是在循环中等待。

条件等待的三种形式(可中断,不可中断和定时)在某些平台上的实现容易程度和性能特征可能会有所不同。 特别是,可能很难提供这些功能并维护特定的语义,例如排序保证。 此外,中断实际挂起的线程的能力可能并不总是在所有平台上都可行。因此,不需要实现为所有三种等待形式定义完全相同的保证或语义,也不需要支持中断实际挂起的线程。

一个实现需要来清楚地记录为每个等待方法提供的语义和保证,并且当实现不支持中断挂起的线程时,它必须遵守此接口(Condition接口)中定义的中断语义。

由于中断通常意味着取消,并且通常不经常进行中断检查,因此与正常方法返回相比,实现可能更喜欢对中断做出响应。 即使可以证明中断发生在另一个可能解除线程阻塞的操作之后,也是如此。 实现应记录此行为。

扫描二维码关注公众号,回复: 12777542 查看本文章

二、AbstractQueuedSynchronizer中ConditionObject源码分析

Node firstWaite:单向链表第一个节点

Node lastWaiter:单向链表最后一个节点

await()方法:先分析到阻塞

方法过程简述:

       1如果当前线程被中断,则抛出InterruptedException。
       2保存getState返回的锁定状态。
       3以保存状态作为参数调用release ,如果失败则抛出IllegalMonitorStateException。
       4阻塞,直到发出信号或被中断为止。
       5通过调用以保存状态作为参数的acquire专用版本来进行重新acquire 。
       6如果在步骤4中被阻止而被中断,则抛出InterruptedException

    public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

1.addConditionWaiter:将节点添加到条件队列队尾。注意:这里单项链表用的是Node的nextWaiter 而不是next,这两个是用来区分是阻塞队列还是条件队列

   private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            //为节点不为空且waitStatus值不是等待(保证新节点前一个节点必须是等待状态),
            //则移除队列中已经取消等待的节点
            //(取消过程中尾节点可能已经重新指向其它堆内存,所以t需要再次指向尾节点)
            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;
        }

2.fullyRelease:完全释放锁,返回值savedState是释放锁之前的 state 值。await() 之前,当前线程是必须持有锁的,这里肯定要释放掉。因为await等价于object的wait,需要释放当前锁,对于可重入锁来说,state值代表了lock() 操作次数,要完全释放锁,必须将state值置为0。如果在释放过程中发生中断,则当前节点的waitStatus会被置为取消(Node.CANCELLED),当下一个节点来时,就会触发上述移除队列中已经取消等待的节点

  当它被唤醒的时候,它需要重新持有 savedState 把锁,才能继续下去。

3.isOnSyncQueue:判断是否在阻塞队列中,不在的话挂起当前线程。(被唤醒后会从条件队列移动到阻塞队列中)

3.1 waitStatus为等待条件值或者没有前置节点(阻塞队列一定有前置节点)则说明不在阻塞队列中。
       3.2 如果后置节点不为空,则一定在阻塞队列中(阻塞队列是next,条件队列是nextWaiter )
       3.3 findNodeFromTail:从阻塞队列为节点开始向前循环查找是否和当前节点内存地址一样,一样则说明存在。

   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;

        return findNodeFromTail(node);
    }

signal()方法:

将等待时间最长的线程(如果存在)从该条件的等待队列移至拥有锁的等待队列。

     public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

当前线程不是锁的持有者抛出异常。 是的话执行唤醒。

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

1.将firstWaiter指向指向 first 节点后面的第一个,因为 first 节点马上要转移了(从该条件的等待队列移至拥有锁的等待队列)。如果没有下一个等待节点,说明等待队列中只有一个节点。所以需要将lastWaiter也指向null。

2.因为first需要转移,所以不能再呆在条件队列中,不能和条件队列有关系,所以需要将first的nextWaiter指向null.

3.循环判断当前first是否转移成功。如果没有转移成功(转移之前被取消)且有下一个waiter(第1步firstWaiter已经指向了nextWaiter,也就是新的firstWaiter),则执行1步。如果转移成功或没有waiter则退出循环。

#transferForSignal:将节点从条件队列转移到同步队列。 如果成功,则返回true。否则false,表示节点在signal之前被取消

    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

1.将节点waitStatus从等待变为0,如果失败,代表已被取消

2.如果成功置为0,则将该节点添加到阻塞队列的队尾。enq(node)返回的是当前节点的前一个节点

3.如果前一个节点值大于0,说明前一个节点被取消,直接唤醒当前节点的线程。或者前一个节点waitStatus<=0,且将前一个节点waitStatus置为Node.SIGNAL(-1代表前一个节点释放锁时需要唤醒它的下一个节点)失败,则直接唤醒当前节点的线程。正常情况下这两个条件都不会成立,所以一般不会直接唤醒当前节点线程,直接返回true,当前节点加入到阻塞队列中等待回去到锁。

接着看await()方法中被唤醒后的后续代码

     while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }

#checkInterruptWhileWaiting :检查当前节点在挂起期间是否有中断,如果在发出信号(signal())之前被中断,则返回THROW_IE;在发出信号之后,则返回REINTERRUPT;否则,则返回0。

  private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }

THROW_IE:-1,发生过中断,且wait退出的时候,需要抛出InterruptedException异常

REINTERRUPT:1,发生过中断,且wait退出的时候,需要重新设置中断状态

0:代表没有发生过中断

#transferAfterCancelledWait:如有必要,将取消等待的节点转移到同步队列。 如果线程在发出信号之前被取消,则返回true(此方法只有在上述方法判断线程确实处于中断状态才会调用)

    final boolean transferAfterCancelledWait(Node node) {
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            enq(node);
            return true;
        }
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }

1.将当前节点的waitStatus从Node.CONDITION(-2)设置为0,如果成功则代表是在signal之前发生的中断,因为signal方法会将waitStatus置为0,显然如果signal成功置为0,这里设置为0就会失败。

2.第1步不成功设置为0,代表是在signal之前发生的中断,从signal转移节点到阻塞队列方法可以看出,如果signal之前中断的话,是不会转移成功的。这里将该节点加入到阻塞队列中。

3.第1步成功,代表是在signal之后发生的中断, 则signal方法会将waitStatus置为0成功。这里while循环仅代表signal方法转移入队还未完成时,自旋等待完成。这种情况可能性很低。

可以看出,被中断的线程是会主动添加到阻塞队列中的,上述await方法中while循环退出的条件就是要么中断,要么成功加入到阻塞队列。

接着看await发放while退出之后的代码

            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);

1.acquireQueued方法方法返回代表当前线程已经获取到锁,且锁的state值为savedState。acquireQueued方法返回true,代表被中断了,如果是signal之前中断的,将interruptMode设置为REINTERRUPT,用于待会重新中断。

2.node.nextWaiter != null,这种情况就是在signal之前中断,也就是没有走signal方法,那么node.nextWaiter就不是null,signal方法会将node.nextWaiter设置为null。不为空则移除队列中已经取消等待的节点

3.处理中断状态。根据interruptMode ,抛出InterruptedException,重新中断当前线程或不执行任何操作。THROW_IE:await 方法抛出 InterruptedException,表示await() 期间发生了中断;REINTERRUPT:重新中断当前线程,表示await() 期间没有被中断,而是 signal() 以后发生的中断

猜你喜欢

转载自blog.csdn.net/sinat_33472737/article/details/114885786