AQS源码深入分析之条件队列-阻塞队列是如何实现的吗?

1 简介

AQS源码深入分析之条件队列-阻塞队列是如何实现的吗?_第1张图片

因为CLH队列中的线程,什么线程获取到锁,什么线程进入队列排队,什么线程释放锁,这些都是不受我们控制的。所以条件队列的出现为我们提供了主动式地、只有满足指定的条件后才能线程阻塞和唤醒的方式。对于条件队列首先需要说明一些概念:条件队列是AQS中除了CLH队列之外的另一种队列,每创建一个Condition实际上就是创建了一个条件队列,而每调用一次await方法实际上就是往条件队列中入队,每调用一次signal方法实际上就是往条件队列中出队。不像CLH队列上节点的状态有多个,条件队列上节点的状态只有一个:CONDITION。所以如果条件队列上一个节点不再是CONDITION状态时,就意味着这个节点该出队了。需要注意的是,条件队列只能运行在独占模式下

一般在使用条件队列作为阻塞队列来使用时都会创建两个条件队列:notFullnotEmpty。notFull表示当条件队列已满的时候,put方法会处于等待状态,直到队列没满;notEmpty表示当条件队列为空的时候,take方法会处于等待状态,直到队列有数据了。

而notFull.signal方法和notEmpty.signal方法会将条件队列上的节点移到CLH队列中(每次只转移一个)。也就是说,存在一个节点从条件队列被转移到CLH队列的情况发生。同时也意味着,条件队列上不会发生锁资源竞争,所有的锁竞争都是发生在CLH队列上的

其他一些条件队列和CLH队列之间的差异如下:

  • 条件队列使用nextWaiter指针来指向下一个节点,是一个单向链表结构,不同于CLH队列的双向链表结构;
  • 条件队列使用firstWaiter和lastWaiter来指向头尾指针,不同于CLH队列的head和tail;
  • 条件队列中的第一个节点也不会像CLH队列一样,是一个特殊的空节点;
  • 不同于CLH队列中会用很多的CAS操作来控制并发,条件队列进队列的前提是已经获取到了独占锁资源,所以很多地方不需要考虑并发。

下面就是具体的源码分析了。条件队列以ArrayBlockingQueue来举例:


2 构造器

 1  /**
 2   * ArrayBlockingQueue:
 3   */
 4  public ArrayBlockingQueue(int capacity) {
 5    this(capacity, false);
 6}
 7
 8  public ArrayBlockingQueue(int capacity, boolean fair) {
 9    if (capacity <= 0)
10        throw new IllegalArgumentException();
11    //存放实际数据的数组
12    this.items = new Object[capacity];
13    //独占锁使用ReentrantLock来实现(fair表示的就是公平锁还是非公平锁,默认为非公平锁)
14    lock = new ReentrantLock(fair);
15    //notEmpty条件队列
16    notEmpty = lock.newCondition();
17    //notFull条件队列
18    notFull = lock.newCondition();
19  }

3 put方法

  1  /**
  2   * ArrayBlockingQueue:
  3   */
  4  public void put(E e) throws InterruptedException {
  5    //非空校验
  6    checkNotNull(e);
  7    final ReentrantLock lock = this.lock;
  8    /*
  9    获取独占锁资源,响应中断模式。其实现代码和lock方法还有Semaphore的acquire方法是类似的
 10    因为这里分析的是条件队列,于是就不再分析该方法的细节了
 11     */
 12    lock.lockInterruptibly();
 13    try {
 14        while (count == items.length)
 15            //如果数组中数据已经满了的话,就在notFull中入队一个新节点,并阻塞当前线程
 16            notFull.await();
 17        //添加数组元素并唤醒notEmpty
 18        enqueue(e);
 19    } finally {
 20        //释放锁资源
 21        lock.unlock();
 22    }
 23  }

4 await方法

如果在put的时候发现数组已满,或者在take的时候发现数组是空的,就会调用await方法来将当前节点放入条件队列中:

 1  /**
 2   * AbstractQueuedSynchronizer:
 3   */
 4  public final void await() throws InterruptedException {
 5    //如果当前线程被中断就抛出异常
 6    if (Thread.interrupted())
 7        throw new InterruptedException();
 8    //把当前节点加入到条件队列中
 9    Node node = addConditionWaiter();
10    //释放之前获取到的锁资源,因为后续会阻塞该线程,所以如果不释放的话,其他线程将会等待该线程被唤醒
11    int savedState = fullyRelease(node);
12    int interruptMode = 0;
13    //如果当前节点不在CLH队列中则阻塞住,等待unpark唤醒
14    while (!isOnSyncQueue(node)) {
15        LockSupport.park(this);
16     

你可能感兴趣的:(后端,大数据,java,容器,docker,运维)