为什么用 CLH 队列?它的优势是什么?
                           
天天向上
发布: 2025-07-20 10:51:52

原创
410 人浏览过

CLH 队列Craig, Landin, and Hagersten Queue)是 Java 并发包 AbstractQueuedSynchronizer(AQS) 用来实现阻塞同步器(如 ReentrantLockCountDownLatch 等)的核心等待队列模型


一、什么是 CLH 队列?

CLH(Craig–Landin–Hagersten)队列 是一种 基于链表的自旋锁等待队列,设计目的是为了减少锁竞争缓存一致性问题

AQS 在实现同步器时,采用的是一个变种的 CLH 队列,用于管理所有等待获取锁的线程。


二、CLH 队列的结构(AQS 中的实现)

  • 采用 双向链表
  • 每个线程都有一个 Node 节点
  • 入队顺序代表加锁顺序,类似 FIFO。
  • 当前线程只关心它前驱节点的状态(如是否被唤醒)。

简图(线程等待队列):

+------+     +------+     +------+     +------+
| head | --> | T1   | --> | T2   | --> | T3   |
+------+     +------+     +------+     +------+

其中每个 Node 有如下主要字段:

static final class Node {
    volatile Node prev;    // 前驱节点
    volatile Node next;    // 后继节点
    volatile Thread thread; // 当前节点对应线程
    volatile int waitStatus; // -1表示SIGNAL,代表前驱释放时需唤醒它
}

三、AQS 为什么采用 CLH 队列?

核心优势如下:

优势说明
1. 非阻塞/减少锁竞争每个线程只关注前驱节点状态,不需遍历整个队列或抢锁
2. 高性能的缓存局部性每个线程自旋在本地变量上(或共享前驱),减少缓存一致性问题
3. 支持公平性队列管理FIFO 结构天然支持公平加锁(如公平锁)
4. 内存占用低不为每个线程创建额外的锁对象,只维护 Node 链表
5. 灵活扩展可通过 waitStatus 控制信号量、读写锁、条件变量等功能
6. 避免虚假唤醒节点只在真正释放锁后被唤醒,逻辑清晰且精确

四、与其他队列的对比

队列模型特点AQS 是否使用?
CLH前驱通知自己,结构紧凑✅ 是
MCS前驱通知后继,线程本地自旋❌ 否(适合 NUMA 系统)
CAS-based无队列,竞争激烈,适合低并发场景❌ 否

CLH 比 MCS 更适合共享缓存系统(如 JVM 内运行环境),原因是:

  • CLH 节点的状态共享较少,线程只读取前驱状态,减少总线通信
  • MCS 要通知后继,容易引发 false sharing(伪共享)问题

五、源码证明(入队逻辑)

// AQS.enqueue(Node node)
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // 队列为空,初始化
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

入队操作使用 CAS 保证线程安全,不会阻塞其他线程,这正是 CLH 等自旋队列的设计思想。


六、图示理解 CLH 的自旋行为

        +----------+      +----------+      +----------+
 head → |   Node   | -->  | Thread A | -->  | Thread B |
        +----------+      +----------+      +----------+
                                 ↑
                           Thread B 自旋在 A 的状态上
  • Thread B 加入队列时关注前驱 A。
  • Thread A 释放锁后唤醒 B。

七、实际效果对比(性能和响应)

特性CLH 队列表现
低争用时延迟⬇️ 极低,几乎无阻塞
高并发⬆️ 高效且公平
可扩展性✅ 支持 Condition、共享锁、写锁

八、参考资料

  • 《Java 并发编程实战》第 14 章:同步器框架
  • 《深入理解 Java 虚拟机》第 13 章:同步机制
  • OpenJDK AQS 源码(GitHub)
  • CLH 原始论文: “Scalable Queue-based Spin Locks with Timeout Support” — M. Scott et al.
  • Wikipedia – CLH lock

总结口诀

AQS 用 CLH,排队抢锁不拥挤;
自旋只看前节点,性能公平又高效!


更多详细内容请关注其他相关文章!

发表回复 0

Your email address will not be published. Required fields are marked *