CLH lock 原理及JAVA实现
--喜欢记得关注我哟【shoshana】--
前记
JUC中的Lock中最核心的类AQS,其中AQS使用到了CLH队列的变种,故来研究一下CLH队列的原理及JAVA实现
一. CLH背景知识
CLH 锁的名字也与他们的发明人的名字相关:Craig,Landin and Hagersten。
CLH Lock摘要
CLH lock is Craig, Landin, and Hagersten (CLH) locks, CLH lock is a spin lock, can ensure no hunger, provide fairness first come first service.
The CLH lock is a scalable, high performance, fairness and spin lock based on the list, the application thread spin only on a local variable, it constantly polling the precursor state, if it is found that the pre release lock end spin.
CLH锁是自旋锁的一种,对它的研究是因为AQS源代码中使用了CLH锁的一个变种,为了更好的理解AQS中使用锁的思想,所以决定先好好理解CLH锁
二. CLH原理
CLH也是一种基于单向链表(隐式创建)的高性能、公平的自旋锁,申请加锁的线程只需要在其前驱节点的本地变量上自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。
三. Java代码实现
类图
- public interface Lock {
- void lock();
- void unlock();
- }
- public class QNode {
- volatile boolean locked;
- }
- import java.util.concurrent.atomic.AtomicReference;
- public class CLHLock implements Lock {
- // 尾巴,是所有线程共有的一个。所有线程进来后,把自己设置为tail
- private final AtomicReference<QNode> tail;
- // 前驱节点,每个线程独有一个。
- private final ThreadLocal<QNode> myPred;
- // 当前节点,表示自己,每个线程独有一个。
- private final ThreadLocal<QNode> myNode;
- public CLHLock() {
- this.tail = new AtomicReference<QNode>(new QNode());
- this.myNode = new ThreadLocal<QNode>() {
- protected QNode initialValue() {
- return new QNode();
- }
- };
- this.myPred = new ThreadLocal<QNode>();
- }
- @Override
- public void lock() {
- // 获取当前线程的代表节点
- QNode node = myNode.get();
- // 将自己的状态设置为true表示获取锁。
- node.locked = true;
- // 将自己放在队列的尾巴,并且返回以前的值。第一次进将获取构造函数中的那个new QNode
- QNode pred = tail.getAndSet(node);
- // 把旧的节点放入前驱节点。
- myPred.set(pred);
- // 判断前驱节点的状态,然后走掉。
- while (pred.locked) {
- }
- }
- @Override
- public void unlock() {
- // unlock. 获取自己的node。把自己的locked设置为false。
- QNode node = myNode.get();
- node.locked = false;
- myNode.set(myPred.get());
- }
- }
简单的看一下CLH的算法定义
the list, the application thread spin only on a local variable, it constantly polling the precursor state, if it is found that the pre release lock end spin.
基于list,线程仅在一个局部变量上自旋,它不断轮询前一个节点状态,如果发现前一个节点释放锁结束.
所以在java中使用了ThreadLocal作为具体实现,AtomicReference为了消除多个线程并发对tail引用Node的影响,核心方法lock()中分为3个步骤去实现
初始状态 tail指向一个node(head)节点
private final AtomicReference<Node> tail = new AtomicReference<Node>(new Node());
thread加入等待队列: tail指向新的Node,同时Prev指向tail之前指向的节点,在java代码中使用了getAndSet即CAS操作使用
Node pred = this.tail.getAndSet(node);
this.prev.set(pred);
寻找当前线程对应的node的前驱node然后开始自旋前驱node的status判断是否可以获取lock
while (pred.locked);
同理unlock()方法,获取当前线程的node,设置lock status,将当前node指向前驱node(这样操作tail指向的就是前驱node等同于出队操作).至此CLH Lock的过程就结束了
测试CLHLock
- public class CLHLockDemo2 {
- public static void main(String[] args) {
- final Kfc kfc = new Kfc();
- for (int i = 0; i < 10; i++) {
- new Thread("eat" + i) {
- public void run() {
- kfc.eat();
- }
- }.start();
- }
- }
- }
- class Kfc {
- private final Lock lock = new CLHLock();
- private int i = 0;
- public void eat() {
- try {
- lock.lock();
- System.out.println(Thread.currentThread().getName() + ": " + --i);
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- lock.unlock();
- }
- }
- public void cook() {
- try {
- lock.lock();
- System.out.println(Thread.currentThread().getName() + ": " + ++i);
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- lock.unlock();
- }
- }
- }
运行结果
- eat1: -1
- eat0: -2
- eat3: -3
- eat4: -4
- eat7: -5
- eat2: -6
- eat5: -7
- eat6: -8
- eat8: -9
- eat9: -10
四. CLH优缺点
CLH队列锁的优点是空间复杂度低(如果有n个线程,L个锁,每个线程每次只获取一个锁,那么需要的存储空间是O(L+n),n个线程有n个myNode,L个锁有L个tail),CLH的一种变体被应用在了JAVA并发框架中。唯一的缺点是在NUMA系统结构下性能很差,在这种系统结构下,每个线程有自己的内存,如果前趋结点的内存位置比较远,自旋判断前趋结点的locked域,性能将大打折扣,但是在SMP系统结构下该法还是非常有效的。一种解决NUMA系统结构的思路是MCS队列锁。
五. 了解与CLH对应的MCS自旋锁
MCS 自旋锁
MCS 的名称来自其发明人的名字:John Mellor-Crummey和Michael Scott。
MCS 的实现是基于链表的,每个申请锁的线程都是链表上的一个节点,这些线程会一直轮询自己的本地变量,来知道它自己是否获得了锁。已经获得了锁的线程在释放锁的时候,负责通知其它线程,这样 CPU 之间缓存的同步操作就减少了很多,仅在线程通知另外一个线程的时候发生,降低了系统总线和内存的开销。实现如下所示:
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class MCSLock {
public static class MCSNode {
volatile MCSNode next;
volatile boolean isWaiting = true; // 默认是在等待锁
}
volatile MCSNode queue;// 指向最后一个申请锁的MCSNode
private static final AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater
.newUpdater(MCSLock.class, MCSNode.class, "queue");
public void lock(MCSNode currentThread) {
MCSNode predecessor = UPDATER.getAndSet(this, currentThread);// step 1
if (predecessor != null) {
predecessor.next = currentThread;// step 2
while (currentThread.isWaiting) {// step 3
}
} else { // 只有一个线程在使用锁,没有前驱来通知它,所以得自己标记自己已获得锁
currentThread.isWaiting = false;
}
}
public void unlock(MCSNode currentThread) {
if (currentThread.isWaiting) {// 锁拥有者进行释放锁才有意义
return;
}
if (currentThread.next == null) {// 检查是否有人排在自己后面
if (UPDATER.compareAndSet(this, currentThread, null)) {// step 4
// compareAndSet返回true表示确实没有人排在自己后面
return;
} else {
// 突然有人排在自己后面了,可能还不知道是谁,下面是等待后续者
// 这里之所以要忙等是因为:step 1执行完后,step 2可能还没执行完
while (currentThread.next == null) { // step 5
}
}
}
currentThread.next.isWaiting = false;
currentThread.next = null;// for GC
}
}
MCS 的能够保证较高的效率,降低不必要的性能消耗,并且它是公平的自旋锁。
CLH 锁与 MCS 锁最大的不同是,MCS 轮询的是当前队列节点的变量,而 CLH 轮询的是当前节点的前驱节点的变量,来判断前一个线程是否释放了锁。
小结
CLH Lock是一种比较简单的自旋锁算法之一,因为锁的CAS操作涉及到了硬件的锁定(锁总线或者是锁内存)所以性能和CPU架构也密不可分,该兴趣的同学可以继续深入研究包括MCS锁等。CLH Lock是独占式锁的一种,并且是不可重入的锁,这篇文章是对AQS锁源代码分析的预热篇
参考内容:
https://segmentfault.com/a/1190000007094429
https://blog.csdn.net/faicm/article/details/80501465
https://blog.csdn.net/aesop_wubo/article/details/7533186
https://www.jianshu.com/p/0f6d3530d46b
https://blog.csdn.net/jjavaboy/article/details/78603477
CLH lock 原理及JAVA实现的更多相关文章
- CLH lock queue的原理解释及Java实现
目录 背景 原理解释 Java代码实现 定义QNode 定义Lock接口 定义CLHLock 使用场景 运行代码 代码输出 代码解释 CLHLock的加锁.释放锁过程 第一个使用CLHLock的线程自 ...
- Java并发包源码学习之AQS框架(二)CLH lock queue和自旋锁
上一篇文章提到AQS是基于CLH lock queue,那么什么是CLH lock queue,说复杂很复杂说简单也简单, 所谓大道至简: CLH lock queue其实就是一个FIFO的队列,队列 ...
- Ticket Lock, CLH Lock, MCS Lock
如果不用OS提供的mutex,我们该如何实现互斥锁?(不考虑重入的情况) 1. naive lock 最简单的想法是,搞一个volatile类型的共享变量flag,值可以是flase(无锁)或者tru ...
- 跳跃表-原理及Java实现
跳跃表-原理及Java实现 引言: 上周现场面试阿里巴巴研发工程师终面,被问到如何让链表的元素查询接近线性时间.笔者苦思良久,缴械投降.面试官告知回去可以看一下跳跃表,遂出此文. 跳跃表的引入 我们知 ...
- 一致性Hash算法原理,java实现,及用途
学习记录: 一致性Hash算法原理及java实现:https://blog.csdn.net/suifeng629/article/details/81567777 一致性Hash算法介绍,原理,及使 ...
- 从使用到原理学习Java线程池
线程池的技术背景 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收. 所 ...
- 自旋锁原理及java自旋锁
一.自旋锁的概念 首先是一种锁,与互斥锁相似,基本作用是用于线程(进程)之间的同步.与普通锁不同的是,一个线程A在获得普通锁后,如果再有线程B试图获取锁,那么这个线程B将会挂起(阻塞):试想下,如果两 ...
- synchronized与static synchronized 的差别、synchronized在JVM底层的实现原理及Java多线程锁理解
本Blog分为例如以下部分: 第一部分:synchronized与static synchronized 的差别 第二部分:JVM底层又是怎样实现synchronized的 第三部分:Java多线程锁 ...
- 【转载】从使用到原理学习Java线程池
线程池的技术背景 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收. 所 ...
随机推荐
- Vue详细介绍模板语法和过滤器的使用!
表达式 {{ XXX }}使用过滤器 {{ XXX | yyy}}使用多个过滤器 {{ XXX | yyy | yyy1}}过滤器带参数 {{ XXX | yyy(123,"zhuiszhu ...
- eclipse IDE 32位汉化方法及常用软件汉化包寻找办法
今天听说小组开发人员遇到安装eclipse不能汉化问题.了解到其他同事用的都是64位操作系统,这个同事用的32位系统.通常情况下常用软件都有各路大神发的成熟汉化包,不会出现无法安装汉化包的情况. 先找 ...
- django framework插件类视图分页
分页 继承APIView类的视图中添加分页 from rest_framework.pagination import PageNumberPagination class MyPageNumberP ...
- A simple introduction to Three kinds of Delegation of Kerberos
1.What is Delegation? Just like the name. Delegation is that a server pretend to behalf of a user an ...
- Shell 编程 函数
本篇主要写一些shell脚本函数的使用. 函数调用 #!/bin/bash sum(){ s=`expr 2 + 3` echo $s } sum [root@localhost ~]# vim su ...
- POJ3070 斐波那契数列递推 矩阵快速幂模板题
题目分析: 对于给出的n,求出斐波那契数列第n项的最后4为数,当n很大的时候,普通的递推会超时,这里介绍用矩阵快速幂解决当递推次数很大时的结果,这里矩阵已经给出,直接计算即可 #include< ...
- 201871010105-曹玉中《面向对象程序设计(java)》第四周学习总结
201871010105-曹玉中<面向对象程序设计(java)>第四周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这 ...
- luoguP5495:Dirichlet 前缀和
题意:给定数组a[]的生成方式,然后b[i]=∑a[j] ,(i%j==0),求所有b[i]的异或和.所有运算%2^32; 思路:高维前缀和的思想,先筛出所有素数,然后把每个素数当成一维,那么分开考 ...
- POJ1463-Strategic game-(树形dp)
http://poj.org/problem?id=1463 题意:有一棵n个结点的树,要在这棵树上放士兵守卫,一个士兵可以守卫自己所在的位置以及与之相邻的点.问最少放多少个士兵? 题解:对于每个点, ...
- 复制excel表中的数据
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...