作者:小傅哥

博客: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的使用,会有不同的优劣效果。在实际的使用中一般默认会选择非公平锁,即使是自旋也是耗费性能的,一般会用在较少等待的线程中,避免自旋时过长。

七、系列推荐

面经手册 · 第16篇《码农会锁,ReentrantLock之公平锁讲解和实现》的更多相关文章

  1. 死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁

    问题 (1)重入锁是什么? (2)ReentrantLock如何实现重入锁? (3)ReentrantLock为什么默认是非公平模式? (4)ReentrantLock除了可重入还有哪些特性? 简介 ...

  2. ReentrantLock 的公平锁源码分析

    ReentrantLock 源码分析   以公平锁源码解析为例: 1:数据结构: 维护Sync 对象的引用:   private final Sync sync; Sync对象继承 AQS,  Syn ...

  3. AQS源码解读(ReentrankLock的公平锁和非公平锁)

    构建Debug代码: 1 package com.hl.interview.lock; 2 3 import java.util.Scanner; 4 import java.util.concurr ...

  4. ReentrantLock之公平锁源码分析

    本文分析的ReentrantLock所对应的Java版本为JDK8. 在阅读本文前,读者应该知道什么是CAS.自旋. 本文大纲 1.ReentrantLock公平锁简介 2.AQS 3.lock方法 ...

  5. 第五章 ReentrantLock源码解析1--获得非公平锁与公平锁lock()

    最常用的方式: int a = 12; //注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁与copyOnWriteArrayList中的全局锁 final Reentrant ...

  6. ReentrantLock 公平锁源码 第0篇

    ReentrantLock 0 关于ReentrantLock的文章其实写过的,但当时写的感觉不是太好,就给删了,那为啥又要再写一遍呢 最近闲着没事想自己写个锁,然后整了几天出来后不是跑丢线程就是和没 ...

  7. concurrent(三)互斥锁ReentrantLock & 源码分析

    参考文档:Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock:http://www.cnblogs.com/skywang12345/p/3496101.html Reentr ...

  8. 面经手册 · 第17篇《码农会锁,ReentrantLock之AQS原理分析和实践使用》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 如果你相信你做什么都能成,你会自信的多! 千万不要总自我否定,尤其是职场的打工人.如 ...

  9. 面经手册 · 第18篇《AQS 共享锁,Semaphore、CountDownLatch,听说数据库连接池可以用到!》

    作者:小傅哥 博客:https://bugstack.cn Github:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有 ...

随机推荐

  1. Python-在列表、字典中筛选数据

    实际问题有哪些? 过滤掉列表[3,9,-1,10.-2......] 中负数 筛选出字典{'li_ming':90,'xiao_hong':60,'li_kang':95,'bei_men':98} ...

  2. gRPC-微服务间通信实践

    微服务间通信常见的两种方式 由于微服务架构慢慢被更多人使用后,迎面而来的问题是如何做好微服务间通信的方案.我们先分析下目前最常用的两种服务间通信方案. gRPC(rpc远程调用) 场景:A服务主动发起 ...

  3. sort函数居然能改变元素值?记一次有趣的Bug——四数之和

    坐标leetcode: 我想都不想直接深度优先搜索暴力求解: class Solution { public: vector<vector<int>> res; //答案 in ...

  4. sqli-labs第三关 详解

    通过第二关,来到第三关 我们用了前两种方法,都报错,然后自己也不太会别的注入,然后莫名的小知识又增加了.这居然是一个带括号的字符型注入, 这里我们需要闭合前面的括号. $sql=select * fr ...

  5. 总线SPI的Arduino库函数

    来源参考:https://www.cnblogs.com/MyAutomation/p/9348480.html 总线SPI的Arduino库函数 SPI基本知识 SPI:高速同步串行口.是一种标准的 ...

  6. appium 环境安装指引

    1.安装Appium-Python-Client Pip install Appium-Python-Client 2.安装nodejs https://nodejs.org/ 安装成功验证:node ...

  7. es使用--新建、删除、增删改数据

    # 进入bin目录 cd /czz/elsearch/bin # 后台启动(不加-d参数则是前台启动,日志在控制台) # 后台启动日志如果不配置,在es目录的logs下面 ./elasticsearc ...

  8. Python+Appium自动化测试(8)-swipe()滑动页面

    app自动化测试过程中,经常会遇到滑动屏幕操作,appium框架的话我们可以使用webdriver提供的swipe()方法来对屏幕页面进行上滑.下滑.左滑.右滑操作. 一,swipe方法介绍 swip ...

  9. 手写一个HTTP框架:两个类实现基本的IoC功能

    jsoncat: 仿 Spring Boot 但不同于 Spring Boot 的一个轻量级的 HTTP 框架 国庆节的时候,我就已经把 jsoncat 的 IoC 功能给写了,具体可以看这篇文章&l ...

  10. [leetcode] 剑指 Offer 专题(一)

    又开了一个笔记专题的坑,未来一两周希望能把<剑指Offer>的题目刷完