面经手册 · 第16篇《码农会锁,ReentrantLock之公平锁讲解和实现》
作者:小傅哥
博客:https://bugstack.cn
专题:面经手册
沉淀、分享、成长,让自己和他人都能有所收获!
一、前言
Java学多少才能找到工作?
最近经常有小伙伴问我,以为我的经验来看,学多少够,好像更多的是看你的野心有多大。如果你只是想找个10k以内的二线城市的工作,那还是比较容易的。也不需要学数据结构、也不需要会算法、也需要懂源码、更不要有多少项目经验。
但反之我遇到一个国内大学TOP2毕业的娃,这货兼职是Offer收割机:腾讯、阿里、字节还有国外新加坡的工作机会等等,薪资待遇也是贼高,可能超过你对白菜价的认知。上学无用、学习无用,纯属扯淡!
你能在这条路上能付出的越多,能努力的越早,收获就会越大!
二、面试题
谢飞机,小记
,刚去冬巴拉泡完脚放松的飞机,因为耐克袜子丢了,骂骂咧咧的赴约面试官。
面试官:咋了,飞机,怎么看上去不高兴。
谢飞机:没事,没事,我心思我学的 synchronized 呢!
面试官:那正好,飞机你会锁吗?
谢飞机:啊。。。我没去会所呀!!!你咋知道
面试官:我说 Java 锁,你想啥呢!你了解公平锁吗,知道怎么实现的吗,给我说说!
谢飞机:公平锁!?嗯,是不 ReentrantLock 中用到了,我怎么感觉似乎有印象,但是不记得了。
面试官:哎,回家搜搜 CLH 吧!
三、ReentrantLock 和 公平锁
1. ReentrantLock 介绍
鉴于上一篇小傅哥已经基于,HotSpot虚拟机源码分析 synchronized 实现和相应核心知识点,本来想在本章直接介绍下 ReentrantLock。但因为 ReentrantLock 知识点较多,因此会分几篇分别讲解,突出每一篇重点,避免猪八戒吞枣。
介绍:ReentrantLock 是一个可重入且独占式锁,具有与 synchronized 监视器(monitor enter、monitor exit)锁基本相同的行为和语意。但与 synchronized 相比,它更加灵活、强大、增加了轮训、超时、中断等高级功能以及可以创建公平和非公平锁。
2. ReentrantLock 知识链条
ReentrantLock 是基于 Lock 实现的可重入锁,所有的 Lock 都是基于 AQS 实现的,AQS 和 Condition 各自维护不同的对象,在使用 Lock 和 Condition 时,其实就是两个队列的互相移动。它所提供的共享锁、互斥锁都是基于对 state 的操作。而它的可重入是因为实现了同步器 Sync,在 Sync 的两个实现类中,包括了公平锁和非公平锁。
这个公平锁的具体实现,就是我们本章节要介绍的重点,了解什么是公平锁、公平锁的具体实现。学习完基础的知识可以更好的理解 ReentrantLock
3. ReentrantLock 公平锁代码
3.1 初始化
ReentrantLock lock = new ReentrantLock(true); // true:公平锁
lock.lock();
try {
// todo
} finally {
lock.unlock();
}
- 初始化构造函数入参,选择是否为初始化公平锁。
- 其实一般情况下并不需要公平锁,除非你的场景中需要保证顺序性。
- 使用 ReentrantLock 切记需要在 finally 中关闭,
lock.unlock()
。
3.2 公平锁、非公平锁,选择
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
- 构造函数中选择公平锁(FairSync)、非公平锁(NonfairSync)。
3.3 hasQueuedPredecessors
static final class FairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
...
}
}
- 公平锁和非公平锁,主要是在方法
tryAcquire
中,是否有!hasQueuedPredecessors()
判断。
3.4 队列首位判断
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
- 在这个判断中主要就是看当前线程是不是同步队列的首位,是:true、否:false
- 这部分就涉及到了公平锁的实现,CLH(Craig,Landin andHagersten)。三个作者的首字母组合
四、什么是公平锁
公平锁就像是马路边上的卫生间,上厕所需要排队。当然如果有人不排队,那么就是非公平锁了,比如领导要先上。
CLH 是一种基于单向链表的高性能、公平的自旋锁。AQS中的队列是CLH变体的虚拟双向队列(FIFO),AQS是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。
为了更好的学习理解 CLH 的原理,就需要有实践的代码。接下来一 CLH 为核心分别介绍4种公平锁的实现,从而掌握最基本的技术栈知识。
五、公平锁实现
1. CLH
1.1 看图说话
1.2 代码实现
public class CLHLock implements Lock {
private final ThreadLocal<CLHLock.Node> prev;
private final ThreadLocal<CLHLock.Node> node;
private final AtomicReference<CLHLock.Node> tail = new AtomicReference<>(new CLHLock.Node());
private static class Node {
private volatile boolean locked;
}
public CLHLock() {
this.prev = ThreadLocal.withInitial(() -> null);
this.node = ThreadLocal.withInitial(CLHLock.Node::new);
}
@Override
public void lock() {
final Node node = this.node.get();
node.locked = true;
Node pred_node = this.tail.getAndSet(node);
this.prev.set(pred_node);
// 自旋
while (pred_node.locked);
}
@Override
public void unlock() {
final Node node = this.node.get();
node.locked = false;
this.node.set(this.prev.get());
}
}
1.3 代码讲解
CLH(Craig,Landin and Hagersten),是一种基于链表的可扩展、高性能、公平的自旋锁。
在这段代码的实现过程中,相当于是虚拟出来一个链表结构,由 AtomicReference 的方法 getAndSet 进行衔接。getAndSet 获取当前元素,设置新的元素
lock()
- 通过
this.node.get()
获取当前节点,并设置 locked 为 true。 - 接着调用
this.tail.getAndSet(node)
,获取当前尾部节点 pred_node,同时把新加入的节点设置成尾部节点。 - 之后就是把 this.prev 设置为之前的尾部节点,也就相当于链路的指向。
- 最后就是自旋
while (pred_node.locked)
,直至程序释放。
unlock()
- 释放锁的过程就是拆链,把释放锁的节点设置为false
node.locked = false
。 - 之后最重要的是把当前节点设置为上一个节点,这样就相当于把自己的节点拆下来了,等着垃圾回收。
CLH
队列锁的优点是空间复杂度低,在SMP(Symmetric Multi-Processor)对称多处理器结构(一台计算机由多个CPU组成,并共享内存和其他资源,所有的CPU都可以平等地访问内存、I/O和外部中断)效果还是不错的。但在 NUMA(Non-Uniform Memory Access) 下效果就不太好了,这部分知识可以自行扩展。
2. MCSLock
2.1 代码实现
public class MCSLock implements Lock {
private AtomicReference<MCSLock.Node> tail = new AtomicReference<>(null);
;
private ThreadLocal<MCSLock.Node> node;
private static class Node {
private volatile boolean locked = false;
private volatile Node next = null;
}
public MCSLock() {
node = ThreadLocal.withInitial(Node::new);
}
@Override
public void lock() {
Node node = this.node.get();
Node preNode = tail.getAndSet(node);
if (null == preNode) {
node.locked = true;
return;
}
node.locked = false;
preNode.next = node;
while (!node.locked) ;
}
@Override
public void unlock() {
Node node = this.node.get();
if (null != node.next) {
node.next.locked = true;
node.next = null;
return;
}
if (tail.compareAndSet(node, null)) {
return;
}
while (node.next == null) ;
}
}
2.1 代码讲解
MCS 来自于发明人名字的首字母: John Mellor-Crummey和Michael Scott。
它也是一种基于链表的可扩展、高性能、公平的自旋锁,但与 CLH 不同。它是真的有下一个节点 next,添加这个真实节点后,它就可以只在本地变量上自旋,而 CLH 是前驱节点的属性上自旋。
因为自旋节点的不同,导致 CLH 更适合于 SMP 架构、MCS 可以适合 NUMA 非一致存储访问架构。你可以想象成 CLH 更需要线程数据在同一块内存上效果才更好,MCS 因为是在本店变量自选,所以无论数据是否分散在不同的CPU模块都没有影响。
代码实现上与 CLH 没有太多差异,这里就不在叙述了,可以看代码学习。
3. TicketLock
3.1 看图说话
3.2 代码实现
public class TicketLock implements Lock {
private AtomicInteger serviceCount = new AtomicInteger(0);
private AtomicInteger ticketCount = new AtomicInteger(0);
private final ThreadLocal<Integer> owner = new ThreadLocal<>();
@Override
public void lock() {
owner.set(ticketCount.getAndIncrement());
while (serviceCount.get() != owner.get());
}
@Override
public void unlock() {
serviceCount.compareAndSet(owner.get(), owner.get() + 1);
owner.remove();
}
}
3.3 代码讲解
TicketLock 就像你去银行、呷哺给你的一个排号卡一样,叫到你号你才能进去。属于严格的公平性实现,但是多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量,每次读写操作都需要进行多处理间的缓存同步,非常消耗系统性能。
代码实现上也比较简单,lock() 中设置拥有者的号牌,并进入自旋比对。unlock() 中使用 CAS 进行解锁操作,并处理移除。
六、总结
- ReentrantLock 是基于 Lock 实现的可重入锁,对于公平锁 CLH 的实现,只是这部分知识的冰山一角,但有这一脚,就可以很好热身便于后续的学习。
- ReentrantLock 使用起来更加灵活,可操作性也更大,但一定要在 finally 中释放锁,目的是保证在获取锁之后,最终能够被释放。同时不要将获取锁的过程写在 try 里面。
- 公平锁的实现依据不同场景和SMP、NUMA的使用,会有不同的优劣效果。在实际的使用中一般默认会选择非公平锁,即使是自旋也是耗费性能的,一般会用在较少等待的线程中,避免自旋时过长。
七、系列推荐
- synchronized 解毒,剖析源码深度分析!
- 面试官,ThreadLocal 你要这么问,我就挂了!
- 扫盲java.util.Collections工具包,学习排序、二分、洗牌、旋转算法
- HashMap核心知识,扰动函数、负载因子、扩容链表拆分
- Netty+JavaFx实战:仿桌面版微信聊天!
面经手册 · 第16篇《码农会锁,ReentrantLock之公平锁讲解和实现》的更多相关文章
- 死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁
问题 (1)重入锁是什么? (2)ReentrantLock如何实现重入锁? (3)ReentrantLock为什么默认是非公平模式? (4)ReentrantLock除了可重入还有哪些特性? 简介 ...
- ReentrantLock 的公平锁源码分析
ReentrantLock 源码分析 以公平锁源码解析为例: 1:数据结构: 维护Sync 对象的引用: private final Sync sync; Sync对象继承 AQS, Syn ...
- AQS源码解读(ReentrankLock的公平锁和非公平锁)
构建Debug代码: 1 package com.hl.interview.lock; 2 3 import java.util.Scanner; 4 import java.util.concurr ...
- ReentrantLock之公平锁源码分析
本文分析的ReentrantLock所对应的Java版本为JDK8. 在阅读本文前,读者应该知道什么是CAS.自旋. 本文大纲 1.ReentrantLock公平锁简介 2.AQS 3.lock方法 ...
- 第五章 ReentrantLock源码解析1--获得非公平锁与公平锁lock()
最常用的方式: int a = 12; //注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁与copyOnWriteArrayList中的全局锁 final Reentrant ...
- ReentrantLock 公平锁源码 第0篇
ReentrantLock 0 关于ReentrantLock的文章其实写过的,但当时写的感觉不是太好,就给删了,那为啥又要再写一遍呢 最近闲着没事想自己写个锁,然后整了几天出来后不是跑丢线程就是和没 ...
- concurrent(三)互斥锁ReentrantLock & 源码分析
参考文档:Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock:http://www.cnblogs.com/skywang12345/p/3496101.html Reentr ...
- 面经手册 · 第17篇《码农会锁,ReentrantLock之AQS原理分析和实践使用》
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 如果你相信你做什么都能成,你会自信的多! 千万不要总自我否定,尤其是职场的打工人.如 ...
- 面经手册 · 第18篇《AQS 共享锁,Semaphore、CountDownLatch,听说数据库连接池可以用到!》
作者:小傅哥 博客:https://bugstack.cn Github:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有 ...
随机推荐
- [vue-webpack-template] webpack配置全局less引入
1. 项目模板webpack vue init webpack <项目名> 2. 安装依赖 除了less所需的less less-loader两个包以外,还需要安装style-resour ...
- 晚间测试3 B. 单(single)
题目描述 单车联通大街小巷.这就是出题人没有写题目背景的原因. 对于一棵树,认为每条边长度为 \(1\),每个点有一个权值\(a[i]\).\(dis(u,v)\)为点\(u\)到\(v\)的最短路径 ...
- 027 01 Android 零基础入门 01 Java基础语法 03 Java运算符 07 逻辑“与”运算符
027 01 Android 零基础入门 01 Java基础语法 03 Java运算符 07 逻辑"与"运算符 本文知识点:Java中的逻辑"与"运算符 逻辑运 ...
- matlab中drawnow更新图窗并处理回调
来源:https://ww2.mathworks.cn/help/matlab/ref/drawnow.html?searchHighlight=drawnow&s_tid=doc_srcht ...
- P4915 帕秋莉的魔导书(动态开点线段树)
题目背景 帕秋莉有一个巨大的图书馆,里面有数以万计的书,其中大部分为魔导书. 题目描述 魔导书是一种需要钥匙才能看得懂的书,然而只有和书写者同等或更高熟练度的人才能看得见钥匙.因此,每本魔导书都有它自 ...
- 【数量技术宅|金融数据分析系列分享】为什么中证500(IC)是最适合长期做多的指数
更多精彩内容,欢迎关注公众号:数量技术宅.探讨数据分析.量化投资问题,请加技术宅微信:sljsz01 投资股票指数相比个股的优势 我们在投资股票的时候,如果持仓集中在一只或者有限几只股票上,恰好不幸遇 ...
- nginx完美支持thinkphp3.2.2(需配置URL_MODEL=>1 pathinfo模式)
来源:http://www.thinkphp.cn/topic/26657.html 第一步:配置SERVER块 server { listen 80; server_name www.domain. ...
- shell-逻辑操作符讲解与文件条件测试多范例多生产案例
1. 逻辑操作符 在书写测试表达式时,可以使用表1.1中的逻辑操作符实现复杂的条件测试 表1.1逻辑连接符 提示: ! 中文意思是反:与一个逻辑值相反的逻辑值 -a 中文意思是与(and & ...
- ansible-基础和安装
什么是ansible ansible是python中的一套模块,系统中的一套自动化工具,可以用来作系统管理.自动化命令.等任务. ansible优势 (1) ansible是python中的一套完整的 ...
- DM9000网卡驱动分析(转)
s3c6410自带的DM9000网卡驱动也是基于platform设备模型. 其定义的设备资源在arch/arm/mach-s3c64xx/mach-smdk6410中.有网卡的resource res ...