本章重点讲解内容如下:

1、什么是CLH同步队列

2、为什么需要CLH同步队列

3、CLH同步队列原理(即队列如何入队、出队)

一 什么是CLH队列

AbstractQueuedSynchronizer类文件开头,作者Doug Lea一大篇幅来介绍CLH队列,大意如下:
  CLH队列是一个FIFO的双向链表:由head、tail、node中间节点组成,每个Node节点包含:thread、waitStatus、next、pre属性

并发编程之CLH同步队列 出队入队详解


  当线程获取同步状态失败后,会将当前线程构造成一个Node节点插入链表(如果第一次插入会初始化head节点为虚拟节点),插入链表都是尾部插入并且setTail为当前节点,同时会阻塞当前线程(调用LockSupport.park方法)。

并发编程之CLH同步队列 出队入队详解



当线程释放同步状态后,会唤醒当前节点的next节点,next节点会抢占同步资源,抢占失败后重新阻塞,成功后next节点会重新setHead为当前线程的节点,将之前的head废弃。

并发编程之CLH同步队列 出队入队详解

二 为什么需要CLH队列

      是为了减少多线程抢占资源造成不必要的cpu上下文切换开销。通过看AQS源码我们知道抢占同步器状态是调用UnSafe.compareAndSwapInt方法,其实底层就是调用的jvm的cas函数。当多个线程同时在cas的时候,最多只能有一个抢占成功,其余的都在自旋,这样就造成了不必要的cpu开销。

     若引入CLH队列队列,至于pre执行完毕,才唤醒next节点,这样最多只有next节点和新进入的线程抢占cpu资源,其余的线程都是阻塞状态,极大的减少了不必要的cpu开销。

三 CLH队列原理(如何入队、出队)

1)入队

入队代码如下:

 1 //获取锁
 2 public final void acquire(int arg) {
 3         //tryAcquire尝试获取锁,Semaphore、coutDownLatch等各个工具类实现不一致
 4         if (!tryAcquire(arg) &&
 5             //acquireQueued:tryAcquire成功就setHead为当前节点,失败则阻塞当前线程
 6             //addWaiter加入同步等待队列
 7             acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
 8             selfInterrupt();
 9 }
10 //加入等待队列
11 private Node addWaiter(Node mode) {
12         Node node = new Node(Thread.currentThread(), mode);
13         // Try the fast path of enq; backup to full enq on failure
14         // 非首次插入,可直接setTail
15         // 设置老的tail为当天tail的pre节点
16         Node pred = tail;
17         if (pred != null) {
18             node.prev = pred;
19             if (compareAndSetTail(pred, node)) {
20                 pred.next = node;
21                 return node;
22             }
23         }
24         //首次插入,需要创建虚拟的head节点
25         enq(node);
26         return node;
27 }
28 private Node enq(final Node node) {
29     for (;;) {
30         Node t = tail;
31         // 如果 tail 是 null,就创建一个虚拟节点,同时指向 head 和 tail,称为 初始化。
32         if (t == null) { // Must initialize
33             if (compareAndSetHead(new Node()))
34                 tail = head;
35         } else {// 如果不是 null
36             // 和 上个方法逻辑一样,将新节点追加到 tail 节点后面,并更新队列的 tail 为新节点。
37             // 只不过这里是死循环的,失败了还可以再来 。
38             node.prev = t;
39             if (compareAndSetTail(t, node)) {
40                 t.next = node;
41                 return t;
42             }
43         }
44     }
45 }
View Code

相关文章: