并发系列(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同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...
随机推荐
- bzoj 2759一个动态树好题
真的是动态树好题,如果把每个点的父亲设成p[x],那么建出来图应该是一个环套树森林,拆掉一条边,就变成了动态树,考虑维护什么,对于LCT上每个节点,维护两组k和b,一组是他到他父亲的,一组是他LCT子 ...
- mac升级后idea提示Can't start git
试了官网的解决方案,还是不行,然后到Stack Overflow上面,找到了下面这个方法,完美解决. 在命令行中运行: xcode-select --install 安装软件就可以了.
- BZOJ_4006_[JLOI2015]管道连接_斯坦纳树
BZOJ_4006_[JLOI2015]管道连接_斯坦纳树 题意: 小铭铭最近进入了某情报部门,该部门正在被如何建立安全的通道连接困扰. 该部门有 n 个情报站,用 1 到 n 的整数编号.给出 m ...
- 【小白学C#】浅谈.NET中的IL代码
一.前言 前几天群里有位水友提问:”C#中,当一个方法所传入的参数是一个静态字段的时候,程序是直接到静态字段拿数据还是从复制的函数栈中拿数据“.其实很明显,这和方法参数的传递方式有关,如果是引用传递的 ...
- 浅析Django之session与cookie
浅析Django之session与cookie 1 session与cookie概述 原理: 由于HTTP协议是无状态,无连接的,当用户发起网路请求时,需要服务端能标识用户ID,用以存储用户相关信息, ...
- 2D图形如何运动模拟出3D效果
一.先看看实现效果图 (左边的2d图片如何运动出右边3d的效果) 引言: 对于这个题目,真的很尴尬,不知道取啥,就想了这个题目 ...
- Android Gradle基于参数化配置实现差异化构建
一.背景: 项目中有一些特殊的需求,如个别渠道集成腾讯bugly,个别渠道集成易观统计,不同的渠道集成不同的推送策略(如Oppo渠道优先Opush推送),不同的渠道拥有不同的第三方登录集成等等.这些需 ...
- Spark2.4.0伪分布式环境搭建
一.搭建环境的前提条件 环境:ubuntu-16.04 hadoop-2.6.0 jdk1.8.0_161. spark-2.4.0-bin-hadoop2.6.这里的环境不一定需要和我一样,基本版 ...
- MySQL 数据库字符集 utf8 和 utf8mb4 的区别
参考于今日头条上Java芋道源码的-----记住:永远不要在 MySQL 中使用 UTF-8 字符集选择 MySQL 的 utf8 实际上不是真正的 UTF-8.utf8 只支持每个字符最多三个字节, ...
- 从壹开始微服务 [ DDD ] 之十 ║领域驱动【实战篇·中】:命令总线Bus分发(一)
烽火 哈喽大家好,老张又见面了,这两天被各个平台的“鸡汤贴”差点乱了心神,博客园如此,简书亦如此,还好群里小伙伴及时提醒,路还很长,这些小事儿就随风而去吧,这周本不打算更了,但是被群里小伙伴“催稿”了 ...