[图解Java]ReentrantLock重入锁
图解ReentrantLock
0. demo
我先给出一个demo, 这样大家就可以根据我给的这段代码, 边调试边看源码了. 还是那句话: 注意"My" , 我把ReentrantLock类 改名为了 "MyReentrantLock"类 , "Lock"类 改名为了"MyLock"类. 大家粘贴我的代码的时候, 把相应的"My"都去掉就好了, 否则会编译报错哦.
import java.util.Scanner;
import java.util.function.Supplier; public class Main {
static final Scanner scanner = new Scanner(System.in);
static volatile String cmd = "";
private static MyReentrantLock lock = new MyReentrantLock(true); public static void main(String[] args) {
for (String name : new String[]{"1", "2", "3", "4", "5", "6"})
new Thread(() -> func(() -> lock, name)).start(); while (scanner.hasNext()) {
cmd = scanner.nextLine();
}
} public static void func(Supplier<MyLock> myLockSupplier, String name) {
blockUntilEquals(() -> cmd, "lock " + name);
myLockSupplier.get().lock();
System.out.println("获取了" + name + "号锁");
blockUntilEquals(() -> cmd, "unlock " + name);
myLockSupplier.get().unlock();
System.out.println("释放了" + name + "号锁");
} private static void blockUntilEquals(Supplier<String> cmdSupplier, final String expect) {
while (!cmdSupplier.get().equals(expect))
quietSleep(1000);
} private static void quietSleep(int mills) {
try {
Thread.sleep(mills);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
使用例子在下面. 首先线程1申请了锁, 成功申请. 然后线程2申请了锁, 未申请到, 进入等待队列中. 线程3 和 线程4 也申请失败, 进入到等待队列中.
随后释放了锁1, 然后锁2就获取到锁了. 然后释放了锁2, 锁3就获取到锁了...然后是锁4. 大概就是这个使用. 用我的这段代码配合着debug, 可以很清楚地调试出代码的执行流程.

1. 开始图解ReentrantLock
一个ReentrantLock()实例里只有一个sync成员变量.

假设咱们创建了一个公平锁, 那么sync是FairSync类的实例.
sync实例里面有四个成员变量.
分别表示:
1. state - 锁计数器
2. exclusiveOwnerThread - 锁的持有线程
3. head - `等待队列`的头结点.
4. tail - 指向`等待队列`的最后一个元素

现在锁是空闲状态.
当线程1申请了锁, 会把state置为1. 然后把锁的exclusiveOwnerThread指向自己(线程1). 这就算是持有锁了.其他线程无法再获取锁了.只能等线程1释放.

如果线程1在此对这个锁执行了lock()方法呢?
那么就是锁的重入了, 也就是说这个线程再次进入(获取)了这个锁 会让state+1.

再重入呢? 那就再加1....
可以重入多少次呢? 可以重入, 直到整形int溢出为止...

接下来, 线程1还没释放锁呢, 线程2就想获取锁了. 那么会发生什么呢:
把线程2封装为一个Node类型的节点. 然后打算把这个Node放到`等待队列`中去.
这个时候`等待队列`才会被建立, 因为这个时候才需要`等待队列`, 这种叫懒初始化.
这个时候, `等待队列`的头结点产生了. 然后把`等待队列`的tail也指向head.
head或者tail 不为null, 表示`等待队列`被创立了.
head==tail 表示, `等待队列`为空, 里面没有`有效元素`.

`等待队列`有了. 线程2对应的Node也有了. 就差把这个Node插入到队尾了.
首先让tail指向线程2对应的Node.
然后分别维护两个Node的前驱和后继.(看下面紫色箭头)

已经将线程2对应的Node插入到`等待队列`的尾部了, 接下来让线程1对应的Node里的waitState等于-1

之后线程2就可以安心的挂起了. 等线程1完全释放锁的时候, 就会唤醒线程2了.
为什么说是`完全释放`呢? 因为锁的的state现在等于3. 需要线程1 unlock()释放3次锁, 才算是完全释放.

接下来, 线程1还没释放锁呢, (线程2也没轮到锁呢). 线程3就想获取锁了. 那么会发生什么呢:
首先会创建一个线程3对应的Node节点.

然后让尾指针tail指向这个最新的Node.
然后维护前驱和后继(紫色箭头), 来维持双向链表.

接下来就会让新节点的前驱节点的waitStatus = -1.
-1表示, 有下一个节点等待被唤醒.

然后线程3就可以安心的挂起了.
等线程2 抢到锁, 用完了释放后, 就会去唤醒线程3.

咱们让线程1 unlock() 一次.
state减1了.
此时, 锁并没有释放, 还是被线程1持有.

咱们再让线程1 unlock() 一次.
state减1了. 但仍然大于0.
此时, 锁并没有释放, 还是被线程1持有.

咱们再让线程1 unlock() 一次.
state减1了. 这回state等于0了. 表示完全释放了锁.
exclusiveOwnerThread也置为了null, 表示当前的锁不被任何线程持有.

准备唤醒下一个, 也就是`等待队列`的第一个元素(线程2)

线程2被唤醒

然后锁的state被置为了1.
锁的exclusiveOwnerThread指向了线程2. 表示当前锁被线程2持有了.

既然线程1已经完全释放锁了. 那么就换线程2来当`等待队列`的头结点.
所以此时, 头结点的含义就是: 当前持有锁的线程对应的Node结点.

然后断开相应的前驱和后继, 让线程1对应的Node完全脱离`等待队列` .

到此, 线程1释放后, 线程2 获取锁的步骤就都执行完了.
接下来, 咱们让线程2释放锁.
state减1后等于0了.
于是锁就完全释放了. exclusiveOwnerThread就被置为null了.

然后是waitStatus被改回了0. 线程2对应的Node马上就要离开`等待队列`了

线程3被唤醒.

让state=1, 并把锁的exclusiveOwnerThread指向自己. 表示线程3自己独占了这把锁.

修改head指针, 并断开相应的前驱和后继链接, 让线程2对应的Node彻底离开`等待队列`

最后, 咱们让线程3释放锁.
state归零.
exclusiveOwnerThread清空.
锁空闲.
而head和tail仍然指向原先的Node. 以后等待队列的头结点就不需要重新初始化了.

[图解Java]ReentrantLock重入锁的更多相关文章
- 轻松学习java可重入锁(ReentrantLock)的实现原理(转 图解)
前言 相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么 ...
- 轻松学习java可重入锁(ReentrantLock)的实现原理
转载自https://blog.csdn.net/yanyan19880509/article/details/52345422,(做了一些补充) 前言 相信学过java的人都知道 synchroni ...
- java 可重入锁ReentrantLock的介绍
一个小例子帮助理解(我们常用的synchronized也是可重入锁) 话说从前有一个村子,在这个村子中有一口水井,家家户户都需要到这口井里打水喝.由于井水有限,大家只能依次打水.为了实现家家有水喝,户 ...
- java高并发系列 - 第12天JUC:ReentrantLock重入锁
java高并发系列 - 第12天JUC:ReentrantLock重入锁 本篇文章开始将juc中常用的一些类,估计会有十来篇. synchronized的局限性 synchronized是java内置 ...
- java并发系列(四)-----源码角度彻底理解ReentrantLock(重入锁)
1.前言 ReentrantLock可以有公平锁和非公平锁的不同实现,只要在构造它的时候传入不同的布尔值,继续跟进下源码我们就能发现,关键在于实例化内部变量sync的方式不同,如下所示: /** * ...
- ReentrantLock(重入锁)以及公平性
ReentrantLock(重入锁)以及公平性 标签(空格分隔): java NIO 如果在绝对时间上,先对锁进行获取的请求一定被先满足,那么这个锁是公平的,反之,是不公平的,也就是说等待时间最长的线 ...
- 从源码角度彻底理解ReentrantLock(重入锁)
目录 1.前言 2.AbstractQueuedSynchronizer介绍 2.1 AQS是构建同步组件的基础 2.2 AQS的内部结构(ReentrantLock的语境下) 3 非公平模式加锁流程 ...
- ReentrantLock(重入锁)的源码解析
转自:从源码角度彻底理解ReentrantLock(重入锁)](https://www.cnblogs.com/takumicx/p/9402021.html)) 公平锁内部是FairSync,非公平 ...
- Java多线程之ReentrantLock重入锁简介与使用教程
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6543947.html 我们知道,线程安全问题需要通过线程之间的同步来解决,而同步大多使用syncrhoize ...
随机推荐
- 2019年3月29日至30日深圳共创力《成功的产品经理DNA》在深圳公开课成功举办
2019年3月29至30日,在深圳南山区中南海滨大酒店10楼行政厅,由深圳市共创力企业管理咨询有限公司举办的<成功的产品经理DNA>公开课成功举办,此次公开课由深圳市共创力咨询资深讲师冯老 ...
- java笔记---- 获取外网(公网)的ip地址
import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import ...
- C# 虚拟串口通信
将主端口COM8拆分成 COM1和COM2两个虚拟端口 COM8接收的消息会传递给COM1和COM2 SerialPort spSend;//spSend,spReceive用虚拟串口连接,它们之间可 ...
- Jenkins 配置 Git 错误解决:CAfile: C:/Program Files/Git/mingw64/ssl/certs/ca-bundle.crt
错误信息: Failed to connect to repository : Command "C:/tools/Git/bin/git.exe ls-remote -h https:/X ...
- 安装mysql8.0.12
安装mysql8.0.12 https://blog.csdn.net/zwj1030711290/article/details/80039780 问题1:忘记记录日志打印的密码就把窗口给关了 解决 ...
- linux系统mysql-5.7 修改字符集
起因:我在网上看修改mysql字符的文章时,都说配置/etc/mysql/my.cnf文件 然而我打开我上述的my.cnf文件时,发现里面的内容跟别人的不一样,我就觉得这个肯定不是正确的文件 经过我在 ...
- ARTS打卡第四周
Algorithm 只出现一次的数字 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次.找出那个只出现了一次的元素. 说明: 你的算法应该具有线性时间复杂度. 你可以不使用 ...
- IO模型介绍
先理解几个问题: (1)为什么读取文件的时候,需要用户进程通过系统调用内核完成(系统不能自己调用内核)什么是用户态和内核态?为什么要区分内核态和用户态呢? 在 CPU 的所有指令中,有些指令是非常危险 ...
- windows环境下mysql密码重置
1.打开cmd窗口,输入命令[mysqld --skip-grant-tables]回车. 2.再打开一个cmd窗口,输入命令[mysql]回车. 3.输入命令[use mysql; ] 连接权限数据 ...
- react-redux的基本用法
注意:读懂本文需要具备redux基础知识, 注明:本文旨在说明如何在实际项目中快速使用react-redux,限于篇幅,本文对具体的原理并未做分析,请参考redux官网 我一直以为我写了一篇关于rea ...