CLH锁 、MCS锁
一。引文
1.1 SMP(Symmetric Multi-Processor)
对称多处理器结构,指服务器中多个CPU对称工作,每个CPU访问内存地址所需时间相同。其主要特征是共享,包含对CPU,内存,I/O等进行共享。
SMP能够保证内存一致性,但这些共享的资源很可能成为性能瓶颈,随着CPU数量的增加,每个CPU都要访问相同的内存资源,可能导致内存访问冲突,
可能会导致CPU资源的浪费。常用的PC机就属于这种。
1.2 NUMA(Non-Uniform Memory Access)
非一致存储访问,将CPU分为CPU模块,每个CPU模块由多个CPU组成,并且具有独立的本地内存、I/O槽口等,模块之间可以通过互联模块相互访问,
访问本地内存的速度将远远高于访问远地内存(系统内其它节点的内存)的速度,这也是非一致存储访问的由来。NUMA较好地解决SMP的扩展问题,
当CPU数量增加时,因为访问远地内存的延时远远超过本地内存,系统性能无法线性增加。
二。CLH
CLH(Craig, Landin, and Hagersten locks): 是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性。
CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。
当一个线程需要获取锁时:
1.创建一个的QNode,将其中的locked设置为true表示需要获取锁
2.线程对tail域调用getAndSet方法,使自己成为队列的尾部,同时获取一个指向其前趋结点的引用myPred
3.该线程就在前趋结点的locked字段上旋转,直到前趋结点释放锁
4.当一个线程需要释放锁时,将当前结点的locked域设置为false,同时回收前趋结点
如下图,线程A需要获取锁,其myNode域为true,tail指向线程A的结点,然后线程B也加入到线程A后面,tail指向线程B的结点。然后线程A和B
都在其myPred域上旋转,一旦它的myPred结点的locked字段变为false,它就可以获取锁。明显线程A的myPred locked域为false,此时线程A获取
到了锁。

三。CLH代码示例
public class CLHLock implements Lock {
AtomicReference<QNode> tail ;
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并发框架中(AbstractQueuedSynchronizer.Node)。CLH在SMP系统结构下
该法是非常有效的。但在NUMA系统结构下,每个线程有自己的内存,如果前趋结点的内存位置比较远,自旋判断前趋结点的locked域,性能
将大打折扣,一种解决NUMA系统结构的思路是MCS队列锁。
五。MCS
MSC与CLH最大的不同并不是链表是显示还是隐式,而是线程自旋的规则不同:CLH是在前趋结点的locked域上自旋等待,而MCS是在自己的
结点的locked域上自旋等待。正因为如此,它解决了CLH在NUMA系统架构中获取locked域状态内存过远的问题。
MCS队列锁的具体实现如下:
a. 队列初始化时没有结点,tail=null
b. 线程A想要获取锁,于是将自己置于队尾,由于它是第一个结点,它的locked域为false
c. 线程B和C相继加入队列,a->next=b,b->next=c。且B和C现在没有获取锁,处于等待状态,所以它们的locked域为true,
尾指针指向线程C对应的结点
d. 线程A释放锁后,顺着它的next指针找到了线程B,并把B的locked域设置为false。这一动作会触发线程B获取锁

六。代码实现
public class MCSLock implements Lock {
AtomicReference<QNode> tail;
ThreadLocal<QNode> myNode;
@Override
public void lock() {
tail = new AtomicReference<QNode>(new QNode());
QNode qnode = myNode.get();
QNode pred = tail.getAndSet(qnode);
if (pred != null) {
qnode.locked = true;
pred.next = qnode;
// wait until predecessor gives up the lock
while (qnode.locked) {
}
}
}
@Override
public void unlock() {
QNode qnode = myNode.get();
if (qnode.next == null) {
if (tail.compareAndSet(qnode, null))
return;
// wait until predecessor fills in its next field
while (qnode.next == null) {
}
}
qnode.next.locked = false;
qnode.next = null;
}
class QNode {
boolean locked = false;
QNode next = null;
}
}
CLH锁 、MCS锁的更多相关文章
- ☕【Java技术指南】「并发原理专题」AQS的技术体系之CLH、MCS锁的原理及实现
背景 SMP(Symmetric Multi-Processor) 对称多处理器结构,它是相对非对称多处理技术而言的.应用十分广泛的并行技术. 在这种架构中,一台计算机由多个CPU组成,并共享内存和其 ...
- MCS锁和CLH锁
CLH锁:自旋锁,在上一个节点上等待,先上代码: public class CLHLock { /** * 保证原子性操作 * */ private AtomicReference<Node&g ...
- 可重入锁 公平锁 读写锁、CLH队列、CLH队列锁、自旋锁、排队自旋锁、MCS锁、CLH锁
1.可重入锁 如果锁具备可重入性,则称作为可重入锁. ========================================== (转)可重入和不可重入 2011-10-04 21:38 这 ...
- 并发系列(3)之 CLH、MCS 队列锁简介
这篇博客主要是作为 AbstractQueuedSynchronizer 的背景知识介绍:平时接触也非常的少,如果你不感兴趣可以跳过:但是了解一下能更加的清楚 AQS 的设计思路: 一.自旋锁简介 通 ...
- 自旋锁、排队自旋锁、MCS锁、CLH锁
转载自:http://coderbee.net/index.php/concurrent/20131115/577 自旋锁(Spin lock) 自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他 ...
- MCS锁——可伸缩的自旋锁
在编写并发同步程序的时候,如果临界区非常小,比如说只有几条或几十条指令,那么我们可以选择自旋锁(spinlock).使用普通的互斥锁会涉及到操作系统的调度,因此小临界区一般首选自旋锁.自旋锁的工作方式 ...
- Ticket Lock, CLH Lock, MCS Lock
如果不用OS提供的mutex,我们该如何实现互斥锁?(不考虑重入的情况) 1. naive lock 最简单的想法是,搞一个volatile类型的共享变量flag,值可以是flase(无锁)或者tru ...
- JUC——线程同步锁(锁处理机制简介)
锁处理机制简介 juc的开发框架解决的核心问题是并发访问和数据安全操作问题,当进行并发访问的时候如果对于锁的控制不当,就会造成死锁这样的阻塞问题. 为了解决这样的缺陷,juc里面重新针对于锁的概念进行 ...
- MYSQL 表级锁 行级锁 页面锁区别
myisam存储引擎默认是表级锁 innodb存储引擎默认是行级锁 DBD存储引擎默认是页面锁 表级锁:开销小,加锁快:不会出现死锁:锁定粒度大,发出锁冲突的概率最高,并发度最低.行级锁:开锁大, ...
- 可重入锁 & 自旋锁 & Java里的AtomicReference和CAS操作 & Linux mutex不可重入
之前还是写过蛮多的关于锁的文章的: http://www.cnblogs.com/charlesblc/p/5994162.html <[转载]Java中的锁机制 synchronized &a ...
随机推荐
- Unity3D脚本18:可视化辅助设置类 Gizmos
Gizmos 类 Gizmos用于场景中给出一个可视化的调试或辅助设置. 所有的Gizmos绘制都必须在脚本的OnDrawGizmos或OnDrawGizmosSelected函数中完成. OnD ...
- 什么是 IntentService
service 默认也运行在 UI 线程,所以里面不能直接做耗时操作,要做耗时操作还得开启子线程来做. IntentService 就是一个 Service, 只不过里面给你默认开启了一个子线程来处理 ...
- ssh org.springframework.beans.TypeMismatchException
这个问题我搜了一上午都木有解决 后来找到一个英文网站 It seems that some other bean (one that is not shown) has a property of t ...
- 匈牙利命名法——命名规范(知道这些再看Windows程序就轻松多了)
匈牙利命名法著名的命名规则当推Microsoft公司的"匈牙利"法,该命名规则的主要思想是"在变量和函数名中加入前缀以增进人们对程序的理解".例如所有的字符变量 ...
- Html5的DeviceOrientation特性
设备定位API 引用W3C中的设备定位API的规范描述可知,该API“……定义了多种新型DOM事件,旨在提供与主机设备相关的物理朝向与运动状态信息.”由API提供的数据产生自多种来源,其中包括设备上的 ...
- jquery数组排序学习
前面转载过一片关于js数组的一些基本能操作方法,本文结合实例对数组排序做简要探讨. 首先看一实例,一般涉及到排序都是动态数据,现在我们自己新建一数组进行模拟. html代码: <!DOCTYPE ...
- LCA的五种解法
标准求法 //O(nlogn)-O(logn) #include<cstdio> #include<algorithm> using namespace std; ; ],to ...
- CentOS mysql硬盘满了挂载阿里云硬盘
前提,昨天晚上导入数据库到本地时候发现硬盘满了,出了,好多错,这边在目录下新建了一个/mysql这样的数据库目录,再将/etc/my.cnf 下的datadir 指向到/mysql下,就可以了 阿里云 ...
- 一些开发遇到的"小问题",你能答对多少?
我会把问题先写在前面,答案用白色字体写在后面.所以用鼠标选择文本就可以看到答案啦. 调用await后因为切换了线程环境(这种说法可能不严谨,但我只能想到这种说法),httpcontext会为null. ...
- IOS第四天(5:创建备份区按钮和判断结果)
创建备份区按钮和判断结果 /** 创建备选区按钮 */ - (void)createOptionButtons:(HMQuestion *)question { // 问题:每次调用下一题方法时,都会 ...