Java并发编程:AQS概述

1 AQS 锁的底层支持

AQS的全称是AbstractQueuedSynchronizer,抽象同步队列。ReentrantLock底层使用了AQS(内部静态类Sync实现了AQS),ThreadPoolExecutor底层也是用了AQS(内部类Worker实现了AQS)。
AQS是一个FIFO的双向队列,其内部通过结点head和tail记录队首和队尾元素。

1.1 内部静态类Node

  • Thread 用来存放进入AQS队列里面的线程
  • SHARED 用来标记该线程是获取共享资源是阻塞挂起后放入AQS队列
  • EXCLUSIVE 用来标记线程是获取独占资源时被挂起后放入AQS队列
  • waitStatus 记录当前线程等待状态
    * CANCELLED 线程取消了
    * SIGNAL 线程需要被唤醒
    * CONDITION 线程在条件队列里等待
    * PROPAGATE 释放共享资源时需要通知其他结点
  • pre 记录当前结点的前驱结点
  • next 记录当前结点的后继结点

1.2 内部类ConditionObject

ConditionObject用来结合锁实现线程同步。ConditionObject可以直接访问AQS对象内部的变量,比如state状态值和AQS队列。ConditionObject是条件变量。每个条件变量对应一个条件队列(单向链表),用来存放调用条件变量的await方法后被阻塞的线程。
条件队列的头部元素是firstWaiter,尾部元素是lastWaiter。

1.3 AQS中成语变量state的作用

 /**
  * The synchronization state.
  */
  private volatile int state; // 使用volatile,保证内存的可见性

对于AQS来说,线程同步的关键是对状态值state进行操作。根据state是否是属于一个线程,操作state的方式分为独占方式和共享方式。以下6个方法的方法参数的值都是state的值。

1.3.1 使用独占方式获取和释放资源的方法

  • public final void acquire(int arg)
  • public final void acquireInterruptibly(int arg)
  • public final boolean release(int arg)

使用独占方式获取的资源是与具体线程绑定的,就是说如果一个线程获取到了资源,就会标记是这个线程获取到了,其他线程再尝试操作state获取资源时会发现当前资源不是自己持有的,就会在获取失败后被阻塞。

1.3.2 使用共享方式获取和释放资源的方法

  • public final void acquireShared(int arg)
  • public final void acquireSharedInterruptibly(int arg)
  • public final boolean releaseShared(int arg)

共享方式是与具体线程是不相关的,当多个线程去请求资源时通过CAS方式竞争获取资源,当一个线程获取到资源后,另外一个线程再次获取资源时,如果当前资源还能满足它的需要,则当前线程只需要使用CAS方式进行获取即可。

2 独占方式下的获取资源与释放资源

2.1 获取资源的方法

当一个线程调用acquire(int arg)获取独占资源时,会首先使用tryAcquire尝试获取资源,具体是设置state的状态,成功则则直接返回,失败则将当前线程封装为类型为Node.EXCLUSIVE的Node结点插入到AQS阻塞队列的尾部,并调用 LockSupport.park()挂起自己。

// 头部结点
private transient volatile Node head;

// 尾部结点
private transient volatile Node tail;

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         // 自我中断
         selfInterrupt();
 }

 // 需要子类去实现
 protected boolean tryAcquire(int arg) {
       throw new UnsupportedOperationException();
 }

 // 添加结点到AQS阻塞队列尾部
 private Node addWaiter(Node mode) {
	   Node node = new Node(Thread.currentThread(), mode);
	   // Try the fast path of enq; backup to full enq on failure
	   // 快速添加到尾部
	   Node pred = tail;
	   if (pred != null) {
	   	   // 设置node的前驱结点
	       node.prev = pred;
	       // 通过CAS设置node为tail结点
	       if (compareAndSetTail(pred, node)) {
	            pred.next = node;
	            return node;
	       }
	   }
	   // 上一步失败,则通过enq入队
	   enq(node);
	   return node;
 }
 
 // 入队操作,双向链表的插入,初始化waitStatus为0
 private Node enq(final Node node) {
       for (;;) {
           Node t = tail;
           if (t == null) { // Must initialize
           	   // 初始化,设置哨兵结点,设置成功,tail也指向哨兵结点
               if (compareAndSetHead(new Node()))
                   tail = head;
           } else {
               // 设置node的前驱结点
               node.prev = t;
               // 通过CAS设置node为tail结点
               if (compareAndSetTail(t, node)) {
                   t.next = node;
                   return t;
               }
           }
       }
 }

 final boolean acquireQueued(final Node node, int arg) {
      // 比较是否成功拿到资源
      boolean failed = true;
       try {
           // 标记等待过程是否被中断
           boolean interrupted = false;
           // 自旋
           for (;;) {
               // 获取当前结点的上一个结点
               final Node p = node.predecessor();
               // 如果p是头结点,并且当前结点尝试获取资源成功
               if (p == head && tryAcquire(arg)) {
                   // 获取资源后,设置该结点为head结点
                   setHead(node);
                   // 此时head的prev和next均为null,方便GC回收之前的head结点,意味获取资源的结点已经出队列了
                   p.next = null; // help GC
                   // 获取资源成功
                   failed = false;
                   // 返回等待过程中是否被中断
                   return interrupted;
               }
               if (shouldParkAfterFailedAcquire(p, node) &&
                   parkAndCheckInterrupt())
                   // 如果等待过程中被中断过,就将interrupted标记为true
                   interrupted = true;
           }
       } finally {
           if (failed)
               // 如果等待过程中没有成功获取资源,就取消结点在队列里的等待
               cancelAcquire(node);
       }
  }

  private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        // 拿到前驱结点的状态
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            
            /*
             * 如果前驱结点已经设置了SIGNAL状态,当前结点就可以安心的挂起
             */
            return true;
        if (ws > 0) {
            /*
             * 如果前驱结点状态不正确,那就一直往前找,直到找到最近一个正常等待的状态的结
             * 点,排在它的后边
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            // 把找到的这个结点的后继结点设为当前结点node
            pred.next = node;
        } else {
            
            /*
             * 通过CAS,把找到的前驱结点的状态设置为SIGNAL
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
  }

  private final boolean parkAndCheckInterrupt() {
      // 调用park()使线程进入waiting状态
      LockSupport.park(this);
      // 如果被唤醒,查看自己是不是被中断的
      return Thread.interrupted();
  }	

park()会让当前线程进入waiting状态。在此状态下,有两种途径可以唤醒该线程:
1)被unpark();2)被interrupt()
Thread.interrupted()会清除当前线程的中断标记位
具体流程如下:
1.调用自定义同步器的tryAcquire()尝试直接去获取资源,如果成功则直接返回;
2.没成功,则addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
3.acquireQueued()使线程在阻塞队列中等待,有机会时(轮到自己,会被unpark())会去尝试获取资源。直至获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
AQS排他模式获取资源的流程图

2.2 释放资源的方法

当一个线程调用release(int arg)方法时会尝试使用tryRelease操作释放资源,这里是设置状态量state的值,然后调用LockSupport.unpark(thread)方法激活AQS队列里面被阻塞的一个线程(thread)。被激活的线程则使用tryAcquire尝试,看当前状态量state是否能满足自己的需要,满足则该线程被激活,然后继续向下执行,否则还是会被放入AQS队列并被挂起。

public final boolean release(int arg) {
      if (tryRelease(arg)) {
           // 找到头结点
           Node h = head;
           if (h != null && h.waitStatus != 0)
               // 唤醒AQS阻塞队列里的下一个线程
               unparkSuccessor(h);
           return true;
       }
       return false;
}

// 需要子类实现
protected boolean tryRelease(int arg) {
      throw new UnsupportedOperationException();
}

// 注意,理解的时候node是当前线程所在的结点
private void unparkSuccessor(Node node) {
       int ws = node.waitStatus;
       // 如果当前线程的waitStatus小于0,就把当前线程的waitStatus通过AQS置为0,允许失败
       if (ws < 0)
           compareAndSetWaitStatus(node, ws, 0);
       //找到下一个需要唤醒的结点s
       Node s = node.next;
       // 如果没空或者已经取消
       if (s == null || s.waitStatus > 0) {
           s = null;
           // 从后面往前找
           for (Node t = tail; t != null && t != node; t = t.prev)
               // 如果找到的结点的waitStatus <= 0,就是有效结点,就把该结点赋给s
               // 找到距离node(当前结点)最近的有效结点,赋值给s,可以看做是FIFO
               if (t.waitStatus <= 0)
                   s = t;
       }
       // 如果结点s不为空,唤醒s结点
       if (s != null)
           LockSupport.unpark(s.thread);
}

2.3 acquire和release的小结

  • AQS并没有提供可用的tryAcquire和tryRelease方法,tryAcquire和tryRelease需要由具体的子类去实现,比如ReentrantLock的内部静态类Sync,ThreadPoolExecutor的内部类Worker
  • unparkSuccessor()和acquireQueued()联系起来一起看
    • 用unpark()唤醒AQS阻塞队列中最前边的,waitStatus<=0的线程s
    • 线程s被唤醒后,进入if (p == head && tryAcquire(arg))的判断,即便即使p!=head也没关系,它会再进入shouldParkAfterFailedAcquire()寻找一个合适的位置。这里既然s已经是AQS等待队列中最前边的那个未放弃线程了,那么通过shouldParkAfterFailedAcquire()的调整,s也必然会跑到head的next结点,下一次自旋p==head就成立
    • s把自己设置成head结点,表示自己已经获取到资源了,acquire()也返回

3 共享方式下的获取资源与释放资源

3.1 获取资源的方法

当线程调用accquireShared(int arg)获取共享资源的时候,会首先使用tryAcquireShared尝试获取资源,具体是设置状态量state的值,成功则会直接返回,失败则将当前线程封装为类型Node.SHARED的Node结点插入到AQS阻塞队列的尾部,LockSupport.park(this)挂起自己。

 public final void acquireShared(int arg) {
      if (tryAcquireShared(arg) < 0)
          doAcquireShared(arg);
 }
	
/**
  *         a negative value on failure; zero if acquisition in shared
  *         mode succeeded but no subsequent shared-mode acquire can
  *         succeed; and a positive value if acquisition in shared
  *         mode succeeded and subsequent shared-mode acquires might
  *         also succeed, in which case a subsequent waiting thread
  *         must check availability.
  */
 // 需要子类去实现
 protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
 }


 private void doAcquireShared(int arg) {
       // 加入到队列尾部
       final Node node = addWaiter(Node.SHARED);
       // 是否成功标志
       boolean failed = true;
       try {
           // 等待过程中是否被中断的标志
           boolean interrupted = false;
           // 自旋
           for (;;) {
               // 获取前驱结点
               final Node p = node.predecessor();
               // 如果p结点是头结点,head结点是拿到资源的结点
               // 此时node被唤醒,很可能head用完资源来唤醒自己
               if (p == head) {
                   // 尝试获取资源
                   int r = tryAcquireShared(arg);
                   // 获取资源成功
                   if (r >= 0) {
                   	   // 将head指向自己,还有剩余资源可以再唤醒之后的线程
                       setHeadAndPropagate(node, r);
                       // 将之前的头结点出队列,方便GC回收
                       p.next = null; // help GC
                       // 如果等待过程中被中断过,将中断补上
                       if (interrupted)
                           selfInterrupt();
                       failed = false;
                       return;
                   }
               }
               // 判断状态,寻找合适的位置,进入waiting状态
               // 等着被unpark()或interrupt()
               if (shouldParkAfterFailedAcquire(p, node) &&
                   parkAndCheckInterrupt())
                   interrupted = true;
           }
       } finally {
           if (failed)
               cancelAcquire(node);
       }
 }

private void setHeadAndPropagate(Node node, int propagate) {
      Node h = head; // Record old head for check below
      // 将head结点指向自己
      setHead(node);
      // 如果还有剩余量,继续唤醒下一个邻居线程
      if (propagate > 0 || h == null || h.waitStatus < 0 ||
          (h = head) == null || h.waitStatus < 0) {
          Node s = node.next;
          if (s == null || s.isShared())
              doReleaseShared();
      }
}

总结一下上面的具体流程:

  • tryAcquireShared()尝试获取资源,成功则直接返回
  • 失败则通过doAcquireShared()进入等待队列park(),直到被unpark()/interrupt()并成功获取到资源才返回。整个等待过程也是忽略中断的。多个线程拿到资源后,还会去唤醒后继结点的操作

强调一点:AQS已经定义好了tryAcquireShared()的返回值

扫描二维码关注公众号,回复: 10689417 查看本文章
  • 负值代表获取失败
  • 0代表获取成功,但没有剩余资源
  • 正数表示获取成功,还有剩余资源,其他线程还可以去获取

留一个思考题:
AQS保证严格按照入队顺序唤醒,保证了公平,但是降低了并发,什么情况下会出现这样的场景呢?

3.2 释放资源的方法

当一个线程调用releaseShared(int arg)时会尝试使用tryReleaseShared操作释放资源,这里是设置状态变量state的值,然后使用LockSupport.unpark(thread)激活AQS阻塞队列里面的一个线程(thread)。被激活的线程则使用tryReleaseShared查看当前状态变量state的值是否能满足自己的需要,满足则该线程被激活,继续向下运行,否则还是会被放入AQS阻塞队列并被挂起。

public final boolean releaseShared(int arg) {
      if (tryReleaseShared(arg)) {
           doReleaseShared();
           return true;
       }
       return false;
}

// 需要子类去实现
protected boolean tryReleaseShared(int arg) {
        throw new UnsupportedOperationException();
}

private void doReleaseShared() {
	  // 自旋	
      for (;;) {
          Node h = head;
          if (h != null && h != tail) {
              int ws = h.waitStatus;
              if (ws == Node.SIGNAL) {
              	  // CAS更改状态失败,继续更改,直至成功
                  if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                      continue;            // loop to recheck cases
                  // 唤醒后继结点
                  unparkSuccessor(h);
              }
              // 如果waitStatus为0,CAS更改waitStatus为PROPAGATE,失败则继续进行
              else if (ws == 0 &&
                       !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                  continue;                // loop on failed CAS
          }
          // head发生改变,继续循环
          if (h == head)                   // loop if head changed
              break;
      }
 }

2.3 acquireShared和releaseShared的小结

AQS并没有提供可用的tryAcquireShared和tryReleaseShared方法,tryAcquireShared和tryReleaseShared需要有具体的子类去实现。子类在实现tryAcquireShared和tryReleaseShared要根据具体场景使用CAS算法尝试修改state状态值,成功返回true,失败返回false。

4 关于方法中带Interruptibly和不带Interruptibly的区别

笔者看源码的时候,独占方式有两个方法,acquire(int arg)和acquireInterruptibly(int arg),共享模式下有两个方法,acquireShared(int arg)和acquireSharedInterruptibly(int arg),那么这里会有什么区别呢?
不带Interruptibly的就是不对中断进行响应,也就是线程在调用了不带Interruptibly关键字的方法获取资源时或者获取资源失败被挂起时,其他线程中断了该线程,那么该线程不会因为被中断而抛出异常,它还是继续获取资源或者被挂起,直白一点,就是不对中断进行响应,忽略中断。
带Interruptibly的方法要对中断进行响应,也就是线程在调用Interruptibly方法获取资源时或者获取资源失败挂起时,其他线程中断了该线程,那么该线程会抛出InterruptedExeception异常而返回。

5 AQS 条件变量

想必读者都知道notify和wait,是配合synchronized内置锁实现线程间同步的基础设施。条件变量的signal和await方法也是用来配合锁(使用AQS实现的锁)实现线程间同步的基础设施。
二者的不同之处
synchronized同时只能与一个共享变量的notify或await方法实现同步
AQS实现的锁,可以对应多个条件变量,多个条件变量就可以有多个条件队列。

研究一下什么是条件队列

// 创建独占锁
public ReentrantLock lock = new ReentrantLock();
// 创建条件变量
public Condition condition = lock.newCondition();

 // 线程1
 lock.lock();
 try {
       System.out.println("begin wait");
       condition.await();
       System.out.println("end wait");
   } catch (InterruptedException e) {
       e.printStackTrace();
   } finally {
       lock.unlock();
   }

  // 线程2
  lock.lock();
   try {
       System.out.println("begin signal");
       condition.signal();
       System.out.println("end signal");
   } catch (Exception e) {
       e.printStackTrace();
   } finally {
       lock.unlock();
   }

线程1首先获取独占锁,调用条件变量的await()方法阻塞挂起当前的线程。当其他线程调用条件变量的signal方法时,被阻塞的线程才会从await出返回。最后线程1释放锁。线程2获取获取锁,调用条件变量的signal()方法,线程1从await出返回,线程2释放锁之后,重新获取锁。
不难分析出,这里的Lock对象等价于synchronized加上共享变量,调用lock.lock()就相当于进入了synchronized块(获取了共享变量的内置锁),调用lock.unlock()就相当于退出了synchronized块。调用条件变量的await()方法就相当于调用共享变量wait()方法,调用条件变量的signal()方法就相当于调用共享变量notify()方法,调用条件变量的signalAll()方法就相当于调用共享变量notifyAll()方法。
其实lock.newCondition()的作用就是new了一个在AQS内部声明的ConditionObject对象,ConditionObject是AQS的内部类,可以访问AQS内部的变量(例如状态变量state)和方法。每个条件变量内部维护了一个条件队列,用来存放调用条件变量的await()方法时被阻塞的线程。注意下,条件队列和AQS阻塞队列不是一回事。
在如下代码中,当线程调用条件变量await()方法时(必须要先调用lock()获取锁),在内部会构造一个类型为Node.CONDITION的node结点,然后将该节点插入到条件队列尾部,之后当前线程会是释放获取的锁(操作锁对应的state变量的值),并被阻塞挂起。这时候如果有其他线程调用了lock.lock()尝试获取锁,就会有一个线程获取到锁,如果获取到锁的线程调用了条件变量的await()方法,则该线程也会被放入到条件变量的阻塞队列里面,然后释放获取到的锁,并在await()方法处阻塞。

 public final void await() throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // 创建新的node结点,并插入到条件队列末尾
        Node node = addConditionWaiter();
        // 释放当前线程获取的锁
        int savedState = fullyRelease(node);
        // 设置中断标志
        int interruptMode = 0;
        // 如果不在AQS阻塞队列
        while (!isOnSyncQueue(node)) {
            // 挂起当前线程
            LockSupport.park(this);
            // 如果线程等待的时候被中断,中断标志的值发生改变,跳出循环
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
        }
        // 从AQS阻塞队列中获取资源,留个疑点,同步队列的结点怎么到了阻塞队列?
        // 通过这里也能看出,条件队列只适合独占锁的方式
        // THROW_IE看下面的解释
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
        // 进行到这里,说明已经成功获取到独占锁
        // 删除条件队列中被取消的节点
        if (node.nextWaiter != null) // clean up if cancelled
            // 遍历清除status不为Node.CONDITION的结点
            unlinkCancelledWaiters();
        // 根据不同模式处理中断
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
 }

 /** 等待退出时抛出InterruptedException异常*/
/** Mode meaning to throw InterruptedException on exit from wait */
private static final int THROW_IE    = -1;

private Node addConditionWaiter() {
      Node t = lastWaiter;
      // If lastWaiter is cancelled, clean out.
      // 如果最后一个节点被取消,则删除队列中被取消的节点
      if (t != null && t.waitStatus != Node.CONDITION) {
          unlinkCancelledWaiters();
          t = lastWaiter;
      }
      // 创建一个类型为CONDITION的节点并加入队列
      Node node = new Node(Thread.currentThread(), Node.CONDITION);
      if (t == null)
          firstWaiter = node;
      else
          t.nextWaiter = node;
      lastWaiter = node;
      return node;
}

// 入参就是当前结点
final int fullyRelease(Node node) {
   // 设置释放锁成功的标志
   boolean failed = true;
   try {
       // 获取当前的state,这里也说明必须是独占方式
       int savedState = getState();
       // 就是独占方式的释放锁
       if (release(savedState)) {
           failed = false;
           // 释放锁成功,返回释放之后的state
           return savedState;
       } else {
       	   // 释放失败,则抛出IllegalMonitorStateException异常
           throw new IllegalMonitorStateException();
       }
   } finally {
       // 最后如果释放锁失败
       if (failed)
       	   // 则把当前结点的状态改为Node.CANCELLED
       	   // 从这里也可以看出,为什么检测最后一个结点的waitStatus
           node.waitStatus = Node.CANCELLED;
   }
}

// 判断是否在同步队列里面
final boolean isOnSyncQueue(Node node) {
     // 快速判断:结点状态或者结点的前驱是不是为null,注:条件队列是单向链表
     if (node.waitStatus == Node.CONDITION || node.prev == null)
         return false;
     // 快速判断:next字段只有同步队列才会使用,条件队列中使用的是nextWaiter字段
     if (node.next != null) // If has successor, it must be on queue
         return true;
     // 上述无法判断,则进入findNodeFromTail进行判断
     return findNodeFromTail(node);
 }

 // 注意这里用的是tail,这是因为条件队列中的节点是被加入到同步队列尾部,这样查找更快
 // 从同步队列尾节点开始向前查找当前节点,如果找到则说明在,否则不在
private boolean findNodeFromTail(Node node) {
     Node t = tail;
     for (;;) {
         if (t == node)
             return true;
         if (t == null)
             return false;
         t = t.prev;
     }
 }


 /** 等待退出时重新中断*/
 /** Mode meaning to reinterrupt on exit from wait */
 private static final int REINTERRUPT =  1;

 /** 等待退出时抛出InterruptedException异常*/
 /** Mode meaning to throw InterruptedException on exit from wait */
 private static final int THROW_IE    = -1;

 // 在条件队列等待的过程中,检查是否被中断
 private int checkInterruptWhileWaiting(Node node) {
    // 如果不是中断,即正常被signal唤醒则返回0
    // 如果结点由中断加入同步队列则返回THROW_IE,由signal加入同步队列则返回REINTERRUPT
     return Thread.interrupted() ?
         (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
         0;
 }

// 被中断后
// 返回true表示结点由中断加入同步队列,返回false表示由signal加入同步队列
final boolean transferAfterCancelledWait(Node node) {
     // CAS设置结点状态为0,如果成功则加入同步队列
     if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
         // 添加结点到AQS阻塞队列尾部
         enq(node);
         return true;
     }
     // 如果上面设置失败,说明节点已经被signal唤醒
     // 由于signal操作会将结点加入AQS阻塞队列,只需自旋等待即可
     while (!isOnSyncQueue(node))
         Thread.yield();
     return false;
 }

 // 根据中断时机选择抛出异常或者设置线程中断状态
 private void reportInterruptAfterWait(int interruptMode)
    throws InterruptedException {
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    else if (interruptMode == REINTERRUPT)
        // 实际上,就是Thread.currentThread().interrupt();
        selfInterrupt();
}
	

如下代码,当另外一个线程调用条件变量的signal方法时(必须先调用锁的lock()方法获取锁),在内部会把条件队列里面的头结点从条件队列里面移除并放入AQS阻塞队列里面,然后激活这个线程。

public final void signal() {
      // 如果不是独占锁,则抛出异常
      if (!isHeldExclusively())
          throw new IllegalMonitorStateException();
      Node first = firstWaiter;
      if (first != null)
      	  // 将条件队列的头元素移动到AQS阻塞队列
          doSignal(first);
 }

// 把一个有效结点从条件队列删除,并把它添加到AQS阻塞队列里面
// 如果失败,则会查找条件队列上等待的下一个结点,直到队列为空
private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

// 将结点加到AQS阻塞队列
final boolean transferForSignal(Node node) {
    // 修改结点状态
    // 如果修改失败只,有一种可能就是该结点被取消
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
    // 添加到AQS阻塞队列,并获得当前结点的前置结点    
    Node p = enq(node);
    int ws = p.waitStatus;
    // 如果前置结点被取消或者修改状态失败则直接唤醒当前节点
    // 此时当前已经在AQS阻塞队列里面,唤醒会进行锁获取或者正确的挂起操作
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

最后总结一下,一个锁对应一个AQS阻塞队列,多个多个条件变量,每个条件变量有自己的一个条件队列。

6 感悟与寄语

学习AQS是一个枯燥漫长的过程,笔者先看了ThreadPoolExecutor和ScheduledThreadPoolExecutor,线程池里的内部类Worker继承了AQS,当时有很多方法不明白,比如tryAcquire(),tryRelease(),为什么通过state变量来控制资源的获取和释放,无法理解Doug Lea写线程池的精髓,经过学习AQS的过程,此有了一点小小的感悟,希望可以给读者带来一点作用。
在这里特别感谢《Java并发编程之美》这本的书的作者,给我理清了思路。还有深入浅出AQS之条件队列这篇文章,把条件队列讲述的明明白白,感谢两位作者的精彩分享。

发布了16 篇原创文章 · 获赞 5 · 访问量 3295

猜你喜欢

转载自blog.csdn.net/qq_32573109/article/details/103144698