原文:http://blog.csdn.net/aesop_wubo/article/details/7533186
CLH锁即Craig, Landin, and Hagersten (CLH) locks,CLH锁是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性。
CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。
SMP(Symmetric Multi-Processor),即对称多处理器结构,指服务器中多个CPU对称工作,每个CPU访问内存地址所需时间相同。其主要特征是共享,包含对CPU,内存,I/O等进行共享。SMP的优点是能够保证内存一致性,缺点是这些共享的资源很可能成为性能瓶颈,随着CPU数量的增加,每个CPU都要访问相同的内存资源,可能导致内存访问冲突,可能会导致CPU资源的浪费。常用的PC机就属于这种。
NUMA(Non-Uniform Memory Access)非一致存储访问,将CPU分为CPU模块,每个CPU模块由多个CPU组成,并且具有独立的本地内存、I/O槽口等,模块之间可以通过互联模块相互访问,访问本地内存的速度将远远高于访问远地内存(系统内其它节点的内存)的速度,这也是非一致存储访问NUMA的由来。NUMA优点是可以较好地解决原来SMP系统的扩展问题,缺点是由于访问远地内存的延时远远超过本地内存,因此当CPU数量增加时,系统性能无法线性增加。
CLH算法实现
CLH队列中的结点QNode中含有一个locked字段,该字段若为true表示该线程需要获取锁,且不释放锁,为false表示线程释放了锁。结点之间是通过隐形的链表相连,之所以叫隐形的链表是因为这些结点之间没有明显的next指针,而是通过myPred所指向的结点的变化情况来影响myNode的行为。CLHLock上还有一个尾指针,始终指向队列的最后一个结点。CLHLock的类图如下所示:
当一个线程需要获取锁时,会创建一个新的QNode,将其中的locked设置为true表示需要获取锁,然后线程对tail域调用getAndSet方法,使自己成为队列的尾部,同时获取一个指向其前趋的引用myPred,然后该线程就在前趋结点的locked字段上旋转,直到前趋结点释放锁。当一个线程需要释放锁时,将当前结点的locked域设置为false,同时回收前趋结点。如下图所示,线程A需要获取锁,其myNode域为true,些时tail指向线程A的结点,然后线程B也加入到线程A后面,tail指向线程B的结点。然后线程A和B都在它的myPred域上旋转,一量它的myPred结点的locked字段变为false,它就可以获取锁扫行。明显线程A的myPred locked域为false,此时线程A获取到了锁。
整个CLH的代码如下,其中用到了ThreadLocal类,将QNode绑定到每一个线程上,同时用到了AtomicReference,对尾指针的修改正是调用它的getAndSet()操作来实现的,它能够保证以原子方式更新对象引用。
- public class CLHLock implements Lock {
- AtomicReference<QNode> tail = new AtomicReference<QNode>(new QNode());
- ThreadLocal<QNode> myPred;
- ThreadLocal<QNode> myNode;
- public CLHLock() {
- tail = new AtomicReference<QNode>(new QNode());
- myNode = new ThreadLocal<QNode>() {
- protected QNode initialValue() {
- return new QNode();
- }
- };
- myPred = new ThreadLocal<QNode>() {
- protected QNode initialValue() {
- return null;
- }
- };
- }
- @Override
- public void lock() {
- QNode qnode = myNode.get();
- qnode.locked = true;
- QNode pred = tail.getAndSet(qnode);
- myPred.set(pred);
- while (pred.locked) {
- }
- }
- @Override
- public void unlock() {
- QNode qnode = myNode.get();
- qnode.locked = false;
- myNode.set(myPred.get());
- }
- }
CLH优缺点
CLH队列锁的优点是空间复杂度低(如果有n个线程,L个锁,每个线程每次只获取一个锁,那么需要的存储空间是O(L+n),n个线程有n个myNode,L个锁有L个tail),CLH的一种变体被应用在了JAVA并发框架中。唯一的缺点是在NUMA系统结构下性能很差,在这种系统结构下,每个线程有自己的内存,如果前趋结点的内存位置比较远,自旋判断前趋结点的locked域,性能将大打折扣,但是在SMP系统结构下该法还是非常有效的。一种解决NUMA系统结构的思路是MCS队列锁。
参考资料:
The Art of Multiprocessor Programming
相关推荐
CLH队列入列是再简单不过了,无非就是tail指向新节点、新节点的prev指向当前最后的节点,当前最后一个节点的next指向当前节点。代码我们可以看看addWaiter(Node node)方法: private Node addWaiter(Node mode) { ...
在线程获取锁时会调用AQS的acquire()方法,该方法第一次尝试获取锁如果失败,会将该线程加入到CLH队列中:public final void acqui
课程详细讨论了不同的锁机制,如TAS锁、TTAS锁、回退锁、队列锁(CLH队列锁和MCS队列锁)以及超时锁。此外,还涉及了管程和阻塞同步,以及读-写锁、可重入锁和信号量等同步机制在链表操作中的应用。 并发数据结构是...
java8 看不到源码DLock - 分布式锁 ...并且重试线程会周期性的唤醒CLH队列的头线程进行重试,这样每个进程只有一个线程可以参与锁竞争,避免了不必要的锁竞争。 同时,还为吞吐量提供了不公平锁。 快速
MCS Lock是一种基于队列的自旋锁机制,它使用一个队列来记录等待获取自旋锁的处理器。当一个处理器尝试获取自旋锁时,它将被添加到队列中,并等待前一个处理器释放自旋锁。 CLH Lock是一种基于链表的自旋锁机制,它...
一、概念 AQS 是 AbstractQueuedSynchronizer 的简称,AQS 是一个抽象的队列式同步器框架,提供了...等到占有线程释放锁后唤醒队列中的任务争抢锁,这个队列为 CLH 队列。 使用state成员变量表示当前的同步状态,提供
总结,ReentrantLock通过AQS和CLH队列实现了一种灵活且高效的锁机制,既支持公平锁又支持非公平锁,同时具备可重入、中断和超时等待的能力。理解和掌握ReentrantLock的工作原理对于编写高并发、高性能的Java程序至关...
在公平锁模式下,线程获取锁的顺序严格按照CLH队列的顺序进行。ReentrantLock的内部结构包含一个Sync对象,Sync是AQS的子类,进一步分为FairSync(公平锁)和NonFairSync(非公平锁)两个子类。 6. **获取公平锁的...
1. **TicketLock(票号锁)**:由Maurice Herlihy提出,是一种基于队列的自旋锁。每个线程在请求锁时会被分配一个票号,然后按照票号的顺序获取锁。这种实现保证了先请求的线程先获得锁,从而实现了公平性。在`code`...
- **CLH队列**(Craig-Landin-Hagersten队列)是一种非阻塞队列实现,采用单向链表结构。 - **特点**: - 单向链表结构,保持FIFO特性。 - 使用原子操作来维护队列结构,提高并发性能。 - 未获取锁的线程通过自旋...
AQS内部通过维护一个CLH队列来管理锁的状态。当线程试图获取锁时,如果获取失败,则会将当前线程的信息封装成一个`Node`节点并加入到同步队列`SyncQueue`中。之后,线程会在循环中不断地尝试获取锁,只有当前节点...
线程在队列中按照FIFO规则等待,当锁被释放时,AQS会使用CLH(自旋锁)队列算法唤醒下一个等待的线程。如果线程在等待过程中被中断,节点的`waitStatus`会被设置为CANCELLED,线程将不再参与同步。 6. **条件变量...
它基于一种称为CLH(Craig, Landin, and Hagersten)队列的等待队列实现,是Java并发包`java.util.concurrent.locks`中的核心类。本文将详细分析AQS的源码,探讨其工作机制,以及在Java中如何实现不同类型的锁。 ...
(2)竞争锁失败,线程封装成节点加入CLH双向链表阻塞队列;(3)线程进行自旋,尝试竞争锁,竞争锁失败,阻塞排队的线程节点,需要等待持有锁的线程释放锁unpark线层或者其他方式唤醒线程,线程再尝试获取锁。 3. ...
12. **AQS入队与出队**:线程申请资源失败时,会被封装成`Node`节点加入队列,通常采用CLH(Craig, Landin, and Hagersten)队列。入队时,新节点连接到傀儡节点,然后成为新的尾节点。出队时,成功获取锁的线程对应...
AQS维护了一个整型的同步状态(state)和一个FIFO的等待队列(基于CLH队列)。线程通过CAS操作尝试改变状态,当资源被占用时,未成功获取资源的线程会被构造成Node节点加入队列等待。AQS通过公平或非公平策略,以及...
3. CLHLock(Craig, Landin, and Hagersten Lock)和MCSLock(Mellor-Crummey and Scott Lock):这两种自旋锁是基于链表结构的公平锁,它们将等待线程组织成一个队列,并使用`AtomicReferenceFieldUpdater`等原子...
2. **等待队列**:AQS使用CLH(Craig, Landin, and Hagersten)队列锁作为其数据结构,这是一个虚拟的双向队列。当线程无法立即获取资源时,会被构造成队列中的节点加入等待。队列的头部(head)主要负责后续调度,...
当一个线程尝试进入被`synchronized`保护的代码段时,如果锁已被其他线程持有,该线程将会被阻塞并进入等待队列,直到锁被释放。 `ReentrantLock`是Java并发包`java.util.concurrent.locks`中的可重入互斥锁,相比`...