博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[图解Java]ReentrantLock重入锁
阅读量:4977 次
发布时间:2019-06-12

本文共 3543 字,大约阅读时间需要 11 分钟。

图解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
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
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. 以后等待队列的头结点就不需要重新初始化了.

转载于:https://www.cnblogs.com/noKing/p/9376982.html

你可能感兴趣的文章
周一04.2流程控制if……else
查看>>
How to Pronounce AR, ORN, etc.
查看>>
linux-存储结构和磁盘划分
查看>>
少年Pi的奇幻漂流
查看>>
java链接集合
查看>>
Cannot see customize report on web analytics(Web analytics customize reports capability missing)
查看>>
【链表】链表基本操作
查看>>
C#数据结构学习笔记之二叉树实现及遍历
查看>>
基于R的拼写纠正器
查看>>
WebService入门实例教程
查看>>
选择iMatrix平台,让女码农更快的适应IT界。
查看>>
「网络流 24 题」搭配飞行员 (二分匹配 最大匹配)
查看>>
hbase相关配置说明
查看>>
第五课时之HTML属性
查看>>
“RESTful架构”相关资料收藏
查看>>
自定义inputformat和outputformat
查看>>
C# 中 var的使用
查看>>
2018..第一天
查看>>
【王俊杰de人工智能实战课】第8次作业
查看>>
习题:输入一组数,用冒泡法排序
查看>>