JUC并发编程基石AQS之Condition解析

从上一篇文章我们知道了,Lock接口代替了synchronize用于并发编程的加锁解锁,那么Object monitor的wait、notify功能谁能代替呢,就是我们这篇文章介绍的Condition。

Object的wait、notify是用来做资源状态判断的,比如生产者-消费者模式,消费者获取到了锁,还需要有资源可以消费才能继续执行。当资源不足时,当前线程可以休眠,不用浪费cpu,当生产者生产后,再通知等待资源的消费者去消费。

代码结构

顶级接口

public interface Condition {
  
  void await() throws InterruptedException;
  
  void signal();
  
  void signalAll();
}

Condition类作为条件控制的顶级接口,JUC为我们提供好的类都实现了此接口。

用法和Object类似,await对应Object的wait方法,signal、signalAll对应Object的notify、notifyAll

public interface Lock {

    void lock();
  
	  void unlock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    Condition newCondition();
}

Lock作为加锁解锁的顶级接口,配合Condition使用,而且必须要获取锁后才能调用Condition提供的方法。

实现

AbstractQueuedSynchronizer类中的内部类ConditionObject实现了Condition接口

public abstract class AbstractQueuedSynchronizer{
  
	 public class ConditionObject implements Condition, java.io.Serializable {
        /** 条件队列的首节点 */
        private transient Node firstWaiter;
        /** 条件队列的尾节点 */
        private transient Node lastWaiter;

使用

以ReentrantLock为例,直接调用ReentrantLock的newCondition就能获得Condition的实例,内部其实的调用的AbstractQueuedSynchronizer的newCondition方法。

public class ReentrantLock implements Lock, java.io.Serializable {
  
  public Condition newCondition() {
        return sync.newCondition();
    }
  
  abstract static class Sync extends AbstractQueuedSynchronizer {
    final ConditionObject newCondition() {
            return new ConditionObject();
        }
  }

代码流程

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

上面是Doug Lea大神在Condition接口给的例子,逻辑很简单,有一个长度为100的Object数组,put方法是将Object的实例加入到数组中,take方法是从数组中获取Object实例,模拟生产者消费者。我们拿这个类来说一下大体流程

例子说明

比如现在有两组线程,p1-p3调用put()方法生产数据

在这里插入图片描述

t1-t3调用take()方法消费数据

在这里插入图片描述

lock

首先不管put和take都需要调用lock获取锁才能执行后面的逻辑。此时根据上次讲的lock的流程,假设t1线程获取到了锁,以t2、p1、p2、t3、p3顺序加入了等待队列。那么此时等待队列的内存结构为:

在这里插入图片描述

t1获取到锁继续执行

take

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

此时数组中还没有数据,t1调用await方法

await

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

addConditionWaiter将线程加入到条件队列中,fullyRelease释放当前线程占用的锁,因为线程已经加入到条件队列需要休眠,所以将之前获取的锁释放掉,这样其他线程才能继续获取锁执行。

while (!isOnSyncQueue(node))判断节点是不是在等待队列中,如果不在则挂起线程。

addConditionWaiter
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    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;
}

构建Node节点,此时状态为Node.CONDITION并加入到条件队列中,如果lastWaiter为空,代表队列为空,设置线程为首节点,否则加入到条件队列队尾。

例子说明

通过上面逻辑,此时将t1线程将入等待队列,此时条件队列数据结构为

在这里插入图片描述

fullyRelease
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;
    }
}

完全释放锁,逻辑可参考上一篇讲的,此时释放锁后,在等待队列中的线程可以继续获取锁。此时等待队列中的t2线程获取锁,继续执行,和t1的逻辑一致,由于数组中还没有对象,继续await。

此时的条件队列的数据结构为

在这里插入图片描述

当t2释放锁后,p1线程获取锁继续执行,调用put方法

isOnSyncQueue
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;
    /*
     * node.prev can be non-null, but not yet on queue because
     * the CAS to place it on queue can fail. So we have to
     * traverse from tail to make sure it actually made it.  It
     * will always be near the tail in calls to this method, and
     * unless the CAS failed (which is unlikely), it will be
     * there, so we hardly ever traverse much.
     */
    return findNodeFromTail(node);
}

判断线程是否在同步队列中,这里我们不考虑中断情况,所以只走第一个if条件判断,判断节点的waitStatus=Node.CONDITION,则说明线程不在同步队列,在条件队列中,返回false。

put

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

此时数组的长度为0,不进while循环,将Object实例放入数组中,调用notEmpty.signal

signal

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

先判断一下当前线程是否获取了锁,没有则抛出异常。再判断条件队列是否为空,不为空则对条件队列进行唤醒。

doSignal

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

将条件队列中的头结点移除并且加入到等待队列中。if条件的逻辑为/如果条件队列只有一个元素的话,则将firstWaiter、lastWaiter置为null,清除对象引用。否则将条件队列头结点向后移动。while循环方法为将节点加入到等待队列中去。

Node节点
static final class Node {
    /** 标明当前节点线程取消排队 */
    static final int CANCELLED =  1;
  
    /** 标明该节点的后置节点需要自己去唤醒 */
    static final int SIGNAL    = -1;
  
    /** 标明当前节点在等待某个条件,此时节点在条件队列中 */
    static final int CONDITION = -2;
    /**
     * waitStatus value to indicate the next acquireShared should
     * unconditionally propagate
     */
    static final int PROPAGATE = -3;

    /**
     * 等待状态,值对于上面的四个常量
     */
    volatile int waitStatus;

    /**
     * 等待队列节点指向的前置节点
     */
    volatile Node prev;

    /**
     * 等待队列节点指向的后置节点
     */
    volatile Node next;

    /**
     * 当前节点持有的线程
     */
    volatile Thread thread;

    /**
     * 条件队列中节点指向的后置节点
     */
    Node nextWaiter;
final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

先将Node节点的对象结构贴出来好观察。首先将节点的waitStatus置为0初始状态。调用enq方法将节点加入到等待队列中去(具体看上一篇的分析),并且返回的是该节点在等待队列中的前置节点(代码中的Node p),然后将前置节点的状态设置为Node.SIGNAL,这个状态是指如果节点的waitStatus为Node.SIGNAL,当节点执行完后需要唤醒你的后置节点。此时等待队列和条件队列的内存结构分别为:

在这里插入图片描述

在这里插入图片描述

到此一个线程获取锁—>进入条件队列—>被唤醒—>进入等待队列整个流程结束。

如有不实,还望指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值