`
wangzhangxing
  • 浏览: 2434 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

(转)CLH队列锁

阅读更多

原文: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()操作来实现的,它能够保证以原子方式更新对象引用。
[java] view plaincopy
 
 
  1. public class CLHLock implements Lock {  
  2.     AtomicReference<QNode> tail = new AtomicReference<QNode>(new QNode());  
  3.     ThreadLocal<QNode> myPred;  
  4.     ThreadLocal<QNode> myNode;  
  5.   
  6.     public CLHLock() {  
  7.         tail = new AtomicReference<QNode>(new QNode());  
  8.         myNode = new ThreadLocal<QNode>() {  
  9.             protected QNode initialValue() {  
  10.                 return new QNode();  
  11.             }  
  12.         };  
  13.         myPred = new ThreadLocal<QNode>() {  
  14.             protected QNode initialValue() {  
  15.                 return null;  
  16.             }  
  17.         };  
  18.     }  
  19.   
  20.     @Override  
  21.     public void lock() {  
  22.         QNode qnode = myNode.get();  
  23.         qnode.locked = true;  
  24.         QNode pred = tail.getAndSet(qnode);  
  25.         myPred.set(pred);  
  26.         while (pred.locked) {  
  27.         }  
  28.     }  
  29.   
  30.     @Override  
  31.     public void unlock() {  
  32.         QNode qnode = myNode.get();  
  33.         qnode.locked = false;  
  34.         myNode.set(myPred.get());  
  35.     }  
  36. }     
从代码中可以看出lock方法中有一个while循环,这 是在等待前趋结点的locked域变为false,这是一个自旋等待的过程。unlock方法很简单,只需要将自己的locked域设置为false即可。
 

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
分享到:
评论

相关推荐

    浅谈Java并发 J.U.C之AQS:CLH同步队列

    CLH队列入列是再简单不过了,无非就是tail指向新节点、新节点的prev指向当前最后的节点,当前最后一个节点的next指向当前节点。代码我们可以看看addWaiter(Node node)方法: private Node addWaiter(Node mode) { ...

    joeylv#joscrapy#【Java并发编程实战】-----AQS(四):CLH同步队列1

    在线程获取锁时会调用AQS的acquire()方法,该方法第一次尝试获取锁如果失败,会将该线程加入到CLH队列中:public final void acqui

    面试必问之AQS原理详解.pdf

    1. **CLH队列**:CLH队列源于自旋锁的实现,但在AQS中被改造成了阻塞锁。线程通过`LockSupport.park()`被阻塞,而`LockSupport.park()`会调用`Unsafe.park()`本地方法,最终在HotSpot VM中通过`pthread_mutex_lock`...

    并发数据结构与多核编程21-22秋季1

    课程详细讨论了不同的锁机制,如TAS锁、TTAS锁、回退锁、队列锁(CLH队列锁和MCS队列锁)以及超时锁。此外,还涉及了管程和阻塞同步,以及读-写锁、可重入锁和信号量等同步机制在链表操作中的应用。 并发数据结构是...

    java8看不到源码-dlock:一种有效可靠的分布式锁

    java8 看不到源码DLock - 分布式锁 ...并且重试线程会周期性的唤醒CLH队列的头线程进行重试,这样每个进程只有一个线程可以参与锁竞争,避免了不必要的锁竞争。 同时,还为吞吐量提供了不公平锁。 快速

    基于SMP的Linux内核自旋锁分析.pdf

    MCS Lock是一种基于队列的自旋锁机制,它使用一个队列来记录等待获取自旋锁的处理器。当一个处理器尝试获取自旋锁时,它将被添加到队列中,并等待前一个处理器释放自旋锁。 CLH Lock是一种基于链表的自旋锁机制,它...

    并发锁核心类AQS学习笔记

    一、概念 AQS 是 AbstractQueuedSynchronizer 的简称,AQS 是一个抽象的队列式同步器框架,提供了...等到占有线程释放锁后唤醒队列中的任务争抢锁,这个队列为 CLH 队列。 使用state成员变量表示当前的同步状态,提供

    ReentrantLock流程浅析

    总结,ReentrantLock通过AQS和CLH队列实现了一种灵活且高效的锁机制,既支持公平锁又支持非公平锁,同时具备可重入、中断和超时等待的能力。理解和掌握ReentrantLock的工作原理对于编写高并发、高性能的Java程序至关...

    Java concurrency之公平锁(一)_动力节点Java学院整理

    在公平锁模式下,线程获取锁的顺序严格按照CLH队列的顺序进行。ReentrantLock的内部结构包含一个Sync对象,Sync是AQS的子类,进一步分为FairSync(公平锁)和NonFairSync(非公平锁)两个子类。 6. **获取公平锁的...

    自旋锁公平性的三种实现代码下载

    1. **TicketLock(票号锁)**:由Maurice Herlihy提出,是一种基于队列的自旋锁。每个线程在请求锁时会被分配一个票号,然后按照票号的顺序获取锁。这种实现保证了先请求的线程先获得锁,从而实现了公平性。在`code`...

    深入理解Java中的AQS.docx

    线程在队列中按照FIFO规则等待,当锁被释放时,AQS会使用CLH(自旋锁)队列算法唤醒下一个等待的线程。如果线程在等待过程中被中断,节点的`waitStatus`会被设置为CANCELLED,线程将不再参与同步。 6. **条件变量...

    7 AQS源码分析.docx

    它基于一种称为CLH(Craig, Landin, and Hagersten)队列的等待队列实现,是Java并发包`java.util.concurrent.locks`中的核心类。本文将详细分析AQS的源码,探讨其工作机制,以及在Java中如何实现不同类型的锁。 ...

    AQS源码阅读笔记,画了两三天的AQS...

    (2)竞争锁失败,线程封装成节点加入CLH双向链表阻塞队列;(3)线程进行自旋,尝试竞争锁,竞争锁失败,阻塞排队的线程节点,需要等待持有锁的线程释放锁unpark线层或者其他方式唤醒线程,线程再尝试获取锁。 3. ...

    Java面试题并发部分.docx

    12. **AQS入队与出队**:线程申请资源失败时,会被封装成`Node`节点加入队列,通常采用CLH(Craig, Landin, and Hagersten)队列。入队时,新节点连接到傀儡节点,然后成为新的尾节点。出队时,成功获取锁的线程对应...

    尚硅谷大厂面试题第三季周阳主讲

    AQS维护了一个整型的同步状态(state)和一个FIFO的等待队列(基于CLH队列)。线程通过CAS操作尝试改变状态,当资源被占用时,未成功获取资源的线程会被构造成Node节点加入队列等待。AQS通过公平或非公平策略,以及...

    Java锁之自旋锁详解

    3. CLHLock(Craig, Landin, and Hagersten Lock)和MCSLock(Mellor-Crummey and Scott Lock):这两种自旋锁是基于链表结构的公平锁,它们将等待线程组织成一个队列,并使用`AtomicReferenceFieldUpdater`等原子...

    java并发编程:juc、aqs

    2. **等待队列**:AQS使用CLH(Craig, Landin, and Hagersten)队列锁作为其数据结构,这是一个虚拟的双向队列。当线程无法立即获取资源时,会被构造成队列中的节点加入等待。队列的头部(head)主要负责后续调度,...

    Java锁之阻塞锁介绍和代码实例

    当一个线程尝试进入被`synchronized`保护的代码段时,如果锁已被其他线程持有,该线程将会被阻塞并进入等待队列,直到锁被释放。 `ReentrantLock`是Java并发包`java.util.concurrent.locks`中的可重入互斥锁,相比`...

    aqs_java_

    4. **CLH锁队列**:AQS的等待队列采用CLH(Craig, Landin, and Hagersten)锁队列结构,这是一种高效的自旋锁实现,每个节点代表一个线程,前驱节点即为阻塞它的线程。 5. **节点状态**:节点状态包括PARKED(已...

    Lock详解.pdf

    在Java中,同步等待队列基于CLH(Craig, Landin, and Hagersten)队列,这是一种高效的双向链表结构。 - **条件等待队列**:通过Condition接口实现,允许线程在满足特定条件时等待,条件满足时再唤醒。Condition...

Global site tag (gtag.js) - Google Analytics