CLH锁学习

CLH锁即Craig, Landin, and Hagersten (CLH) locks,CLH锁是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性。
何谓自旋锁?它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

CLHLock的java实现逻辑:

 

public class CLHLock implements Lock {
    private final AtomicReference tail;
    private final ThreadLocal myPred;
    private final ThreadLocal myNode;

    public CLHLock() {
        tail = new AtomicReference(new QNode());
        myNode = new ThreadLocal() {
            protected QNode initialValue() {
                return new QNode();
            }
        };

        myPred = new ThreadLocal();
    }

    @Override
    public void lock() {
        QNode node = myNode.get();
        node.locked = true;
        QNode pred = tail.getAndSet(node);
        myPred.set(pred);
        while (pred.locked) {
        }
    }

    @Override
    public void unlock() {
        QNode node = myNode.get();
        node.locked = false;
        myNode.set(myPred.get());
    }

    private static class QNode {
        volatile boolean locked;
    }
}

 


锁本身被表示成QNode的虚拟链表。共享的tail保存申请的最后的一个申请lock的线程的myNode域。每个申请lock的线程通过一个本地线程变量pred指向它的前驱。另外一个本地线程变量myNode的locked保存本身的状态,true:正在申请锁或已申请到锁;false:已释放锁。每个线程通过监视自身的前驱状态,自旋等待锁被释放。释放锁时,把自身myNode的locked设为false,使后继线程获的锁,并回收前驱的QNode,等待下次使用,因为自身的QNode可能被tail获后继引用,而前驱不再使用老的QNode。

AbstractQueuedSynchronizer的实现也是部分基于"CLH"队列的思想。具体见AbstractQueuedSynchronizer的doAcquireSharedInterruptibly方法实现
AbstractQueuedSynchronizer 为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁定和相关同步器(信号量、事件,等等)提供一个框架。用java的人都知道synchronized能够对一个需要确保线程安全的对象,方法实现多线程并发控制,这是在java语法层次的实现,而AbstractQueuedSynchronizer 则是在应用层次而不是语法层次(更高的层次)提供了实现多线程并发控制组件的基础。

 

<think>好的,我现在需要向用户介绍CLH队列。首先,我得回想一下自己之前学过的关于和并发控制的知识。CLH队列应该是一种用于实现自旋的队列结构,对吧?我记得它是由Craig、Landin和Hagersten提出的,所以名字是CLH的缩写。 用户可能是在学习多线程或者并发编程时遇到了CLH队列,想要了解它的基本原理和应用场景。他们可能对自旋、公平性、或者如何减少线程竞争感兴趣。我需要先确定CLH队列的结构和工作原理,然后解释它的优缺点,以及它在实际中的应用,比如在Java的并发库中是否有用到。 首先,CLH队列的结构。每个线程在请求的时候会创建一个节点,里面可能包含一个布尔值的locked字段,表示是否在等待。这些节点通过原子引用连接成一个队列,队尾由尾指针指向。当一个线程想要获取时,它会将自己的节点插入队尾,并自旋检查前驱节点的locked状态。当前驱节点释放(即设置locked为false)时,当前线程就可以获取。 接下来,需要考虑线程释放的过程。释放时,线程需要将自己的节点的locked设为false,这样后继线程就能检测到并获取。同时,可能还需要将当前节点的前驱指针断开,避免内存泄漏。这里可能会涉及到原子操作,比如CAS(比较并交换)来确保线程安全。 然后,CLH队列的特点。比如,它是公平的,按照请求的顺序分配,避免了饥饿现象。由于线程自旋在前驱节点的状态上,而不是全局变量,所以在多处理器系统中可以减少总线流量,提高性能。另外,CLH队列适用于NUMA架构吗?可能因为每个线程只关注自己的前驱节点,本地自旋可以减少远程内存访问的开销。 可能的实现细节问题。比如,节点的复用,是否需要避免ABA问题,或者如何处理节点的创建和回收。比如,Java中的AQS(AbstractQueuedSynchronizer)是否使用了类似的机制,但可能不是直接使用CLH,而是结合了其他优化,比如将自旋改为阻塞以减少CPU占用。 用户可能还会想知道CLH其他机制的区别,比如MCS的不同。MCS中每个节点自旋在自己的状态上,而CLH自旋在前驱节点的状态上。这可能影响在不同架构下的性能表现。 在解释过程中,需要确保语言通俗易懂,结构清晰。可能先给出CLH的定义和用途,再逐步分解其结构、工作原理,以及优缺点。最后可以提及其应用实例,比如在Java并发库中的使用情况。 需要验证自己是否正确理解了CLH队列。比如,是否每个线程确实自旋在前驱节点的locked字段?当释放时,当前线程是否修改自己的节点状态,而后继线程如何感知到?可能前驱节点的状态变化会被后继线程轮询到,从而退出自旋,获取。 另外,需要注意CLH队列在Java中的实现可能有所调整,比如AQS使用的是CLH的变体,可能结合了阻塞和自旋,或者使用了虚拟节点来简化操作。因此,在介绍时应该说明CLH的基本原理,并提到实际中的变体应用,避免用户误解所有基于队列的都是CLH的直接实现。 总结下来,回答的结构应该是: 1. CLH队列的定义和背景 2. 结构组成(节点、队列、尾指针) 3. 获取的流程 4. 释放的流程 5. 特点(公平性、自旋方式、性能优势) 6. 其他机制的对比(如MCS) 7. 实际应用举例 需要确保使用中文,避免术语错误,并且用例子或比喻帮助理解。比如,把线程排队比作排队取票,每个线程关注前面的人是否完成,而不是一直询问柜台是否空闲,从而减少拥挤。 最后,检查是否有遗漏点,比如CLH的缺点。比如,在NUMA架构下,如果前驱节点的内存位置较远,自旋可能会带来较高的延迟,这时候MCS可能更合适。不过CLH在SMP架构下表现更好。这也需要提及,以全面展示其适用场景。</think>CLH队列是一种基于链表的**公平自旋**实现,由Craig、Landin和Hagersten提出,主要用于多线程环境中管理对共享资源的访问顺序。其核心思想是通过队列结构按请求顺序分配,确保线程公平性。 --- ### **结构工作原理** 1. **节点结构** 每个线程请求时,会创建一个节点(`QNode`),包含以下字段: - `locked`:布尔值,表示线程是否需要等待(`true`为等待,`false`可获取)。 - `前驱节点(prev)`:指向前一个线程的节点。 2. **队列尾指针** - 所有节点通过原子引用(如`AtomicReference`)构成隐式队列。 - 全局尾指针`tail`总是指向最新加入的节点。 --- ### **获取流程** 1. **创建节点** 线程创建一个新节点,将其`locked`设为`true`。 2. **加入队列** 通过原子操作将当前节点设置为新的`tail`,并获取原`tail`作为前驱节点。 3. **自旋等待** 线程持续自旋检查前驱节点的`locked`字段,直到其变为`false`。 ```plaintext 示例: 线程A → 创建节点A(locked=true) → 加入队列(tail指向A) 线程B → 创建节点B(locked=true) → 更新tail为B,前驱为A → 自旋检查A.locked 当A释放(设置A.locked=false),B检测到后退出自旋,获取。 ``` --- ### **释放流程** 1. **标记可释放** 线程将自身节点的`locked`设为`false`,表示释放。 2. **断开前驱引用** 断开当前节点前驱节点的链接(避免内存泄漏)。 --- ### **核心特点** 1. **公平性** 严格按请求顺序分配,避免线程饥饿。 2. **局部自旋** 线程仅自旋检查前驱节点的状态(而非全局变量),减少缓存同步开销。 3. **适用场景** 在**SMP架构**下性能更优(因前驱节点状态通常在本地缓存中)。 --- ### **对比其他机制** | 特性 | CLH队列 | MCS队列 | |--------------|----------------------------------|----------------------------------| | **自旋对象** | 前驱节点的状态 | 自身节点的状态 | | **内存访问** | 适合SMP(本地缓存友好) | 适合NUMA(减少远程内存访问) | | **实现复杂度** | 较简单(无需维护后继指针) | 较复杂(需维护后继指针) | --- ### **实际应用** - **Java并发库**:`AbstractQueuedSynchronizer`(AQS)的底层参考了CLH思想,但将其改进为支持阻塞唤醒机制(通过`LockSupport.park()`代替自旋)。 - **数据库事务管理**:用于控制事务的串行化访问。 --- ### **总结** CLH队列通过隐式链表和局部自旋,高效实现了公平,尤其适用于高并发且线程竞争不极端激烈的场景。其设计精髓在于将竞争分散到各个节点的本地状态上,而非集中式变量,从而提升系统整体吞吐量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值