并发系列(3)之 CLH、MCS 队列锁简介
这篇博客主要是作为 AbstractQueuedSynchronizer 的背景知识介绍;平时接触也非常的少,如果你不感兴趣可以跳过;但是了解一下能更加的清楚 AQS 的设计思路;
一、自旋锁简介
通常情况下解决多线程共享资源逻辑一致性问题有两种方式:
- 互斥锁:当发现资源被占用的时候,会阻塞自己直到资源解除占用,然后再次尝试获取;
- 自旋锁:当发现占用时,一直尝试获取锁(线程没有被挂起的过程,也就没有线程调度切换的消耗);
对于这两种方式没有优劣之分,只有是否适合当前的场景;具体的对比就不在继续深入了,如果你很感兴趣可以查看 《多处理器编程的艺术》 提取码:rznn ;
但是如果竞争非常激烈的时候,使用自旋锁就会产生一些额外的问题:
- 可能导致一些线程始终无法获取锁(争抢的时候必然是当前活跃线程获得锁的几率大),也就是饥饿现象;
- 因为自旋锁会依赖一个共享的锁标识,所以竞争激烈的时候,锁标识的同步也需要消耗大量的资源;
- 如果要用自旋锁实现公平锁(即先到先获取),此时就还需要额外的变量,也会比较麻烦;
解决这些问题其中的一种办法就是使用队列锁,简单来讲就是让这些线程排队获取;下面我们介绍常用的两种,即 CLH 锁 和 MCS 锁;
二、CLH 锁
CLH 是 Craig、Landin 和 Hagersten 三位作者的缩写,具体内容在 《Building FIFO and Priority-Queuing Spin Locks from Atomic Swap》 论文中有详细介绍,大家可以自行查看;我们 JDK 中 java.util.concurrent.locks.AbstractQueuedSynchronizer 就是根据 CLH 锁的变种实现的;
简单实现:
public class CLH implements Lock {
private final ThreadLocal<Node> preNode = ThreadLocal.withInitial(() -> null);
private final ThreadLocal<Node> node = ThreadLocal.withInitial(Node::new);
private final AtomicReference<Node> tail = new AtomicReference<>(new Node());
private static class Node {
private volatile boolean locked;
}
@Override
public void lock() {
final Node node = this.node.get();
node.locked = true;
Node pre = this.tail.getAndSet(node);
this.preNode.set(pre);
while (pre.locked) ;
}
@Override
public void unlock() {
final Node node = this.node.get();
node.locked = false;
this.node.set(this.preNode.get());
}
}

三、MCS 锁
同样 MCS 是 John M. Mellor-Crummey 和 Michael L. Scott 名字的缩写,具体内容可以在 《Algorithms for Scalable Synchronization on Shared-Memory Multiprocessors》 论文中查看;
简单实现:
public class MCS implements Lock {
private final ThreadLocal<Node> node = ThreadLocal.withInitial(Node::new);
private final AtomicReference<Node> tail = new AtomicReference<>();
private static class Node {
private volatile boolean locked = false;
private volatile Node next = null;
}
@Override
public void lock() {
Node node = this.node.get();
node.locked = true;
Node pre = tail.getAndSet(node);
if (pre != null) {
pre.next = node;
while (node.locked) ;
}
}
@Override
public void unlock() {
Node node = this.node.get();
if (node.next == null) {
if (tail.compareAndSet(node, null)) {
return;
}
while (node.next == null) ;
}
node.next.locked = false;
node.next = null;
}
}

总结
- 以上的代码我已经测试过,大家可以直接拿下来自行实验;
- CLH 锁和 MCS 锁区别主要有两点:1. 链表结构的区别;2. 自旋对象的区别,CLH 是在前驱节点上自旋,而 MCS 是在自身节点上自旋;这里第二点才是最重要的,主要体现在 SMP(Symmetric Multi-Processor) 和 NUMA(Non-Uniform Memory Access) 不同的处理器架构上;这里大家可以自行 Google;
并发系列(3)之 CLH、MCS 队列锁简介的更多相关文章
- Java 并发系列之七:java 阻塞队列(7个)
1. 基本概念 2. 实现原理 3. ArrayBlockingQueue 4. LinkedBlockingQueue 5. LinkedBlockingDeque 6. PriorityBlock ...
- 并发系列(4)之 AbstractQueuedSynchronizer 源码分析
本文将主要讲述 AbstractQueuedSynchronizer 的内部结构和实现逻辑,在看本文之前最好先了解一下 CLH 队列锁,AbstractQueuedSynchronizer 就是根据 ...
- 可重入锁 公平锁 读写锁、CLH队列、CLH队列锁、自旋锁、排队自旋锁、MCS锁、CLH锁
1.可重入锁 如果锁具备可重入性,则称作为可重入锁. ========================================== (转)可重入和不可重入 2011-10-04 21:38 这 ...
- Java 并发编程学习笔记 理解CLH队列锁算法
CLH算法实现 CLH队列中的结点QNode中含有一个locked字段,该字段若为true表示该线程需要获取锁,且不释放锁,为false表示线程释放了锁.结点之间是通过隐形的链表相连,之所以叫隐形的链 ...
- CLH队列锁
http://blog.csdn.net/aesop_wubo/article/details/7533186 CLH锁即Craig, Landin, and Hagersten (CLH) lock ...
- 【Java并发编程实战】-----“J.U.C”:CLH队列锁
在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格按照FIFO的队列.他能够确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...
- 【Java并发编程实战】-----“J.U.C”:CLH队列锁
在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格依照FIFO的队列.他可以确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...
- Java并发包源码学习系列:CLH同步队列及同步资源获取与释放
目录 本篇学习目标 CLH队列的结构 资源获取 入队Node addWaiter(Node mode) 不断尝试Node enq(final Node node) boolean acquireQue ...
- 【Java并发编程实战】----- AQS(四):CLH同步队列
在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...
随机推荐
- 【codeforces 718 C&D】C. Sasha and Array&D. Andrew and Chemistry
C. Sasha and Array 题目大意&题目链接: http://codeforces.com/problemset/problem/718/C 长度为n的正整数数列,有m次操作,$o ...
- BZOJ_3238_[Ahoi2013]差异_后缀自动机
BZOJ_3238_[Ahoi2013]差异_后缀自动机 Description Input 一行,一个字符串S Output 一行,一个整数,表示所求值 Sample Input cacao Sam ...
- BZOJ_3129_[Sdoi2013]方程_组合数学+容斥原理
BZOJ_3129_[Sdoi2013]方程_组合数学+容斥原理 Description 给定方程 X1+X2+. +Xn=M 我们对第l..N1个变量进行一些限制: Xl < = A ...
- 使用ASIFormDataRequest完成用户的登录操作
ASIFormDataRequest是用于向表单post数据或get数据,可以用于用户向服务器端发送请求完成登录注册,上传下载数据的操作. 之前写过一篇文章是介绍使用ios内置的功能完成登录操作(NS ...
- Python&Appium实现滑动引导页进入APP
最近在研究安卓APP的自动化测试.首先遇到的问题是,当一个session建立的时候,最先进入的是欢迎页和引导页,引导页有三张,最后一张上显示"enter"按钮,点击才能进入主界面. ...
- Django 基础二(View和urls)
上一篇博文已经成功安装了python环境和Django,并且新建了一个空的项目.接下来就可以正式开始进行Django下 的Web开发了.首先进入项目的主目录: cd ./DjangoLearn/hol ...
- appium 【已解决】Android,每次启动手机中都会安装Appium settings和Unclock的方法
环境介绍: 1.appium版本:1.4.16.1 2.真机运行 实现结果: 运行appium第一次运行则安装Appium settings和Unclock的apk,再次之后的运行则无需手动卸载,再次 ...
- Charles 连接手机抓包出现Unknown,一直无法抓包的问题解决
mac电脑安装了charles并且确保已经安装成功,https抓包需要安装的相关的证书已经安装,并且手机浏览器中输入chls.pro/ssl已经将证书下载完成,但是手机与电脑相连通过点击手机应用还是无 ...
- spring boot sharding-jdbc实现分佈式读写分离和分库分表的实现
分布式读写分离和分库分表采用sharding-jdbc实现. sharding-jdbc是当当网推出的一款读写分离实现插件,其他的还有mycat,或者纯粹的Aop代码控制实现. 接下面用spring ...
- SpringBoot集成Lombok,应用+源码解析,让代码优雅起来
一.Lombok简介 (1)Lombok官网(https://projectlombok.org/)对lombok的介绍 (2)GitHub项目地址:https://github.com/rzwits ...