[图解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, 可以很清楚地调试出代码的执行流程.

2. 开始图解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

时间: 2024-10-06 00:13:49

[图解Java]ReentrantLock重入锁的相关文章

java 可重入锁ReentrantLock的介绍

一个小例子帮助理解 话说从前有一个村子,在这个村子中有一口水井,家家户户都需要到这口井里打水喝.由于井水有限,大家只能依次打水.为了实现家家有水喝,户户有水用的目标,村长绞尽脑汁,最终想出了一个比较合理的方案. 首先,在水井边上安排一个看井人,负责维持秩序. 然后,打水时,以家庭为单位,哪个家庭任何人先到井边,就可以先打水,而且如果一个家庭占到了打水权,其家人这时候过来打水不用排队.而那些没有抢占到打水权的人,一个一个挨着在井边排成一队,先到的排在前面. 最后,打水的人打完水以后就告诉看井人,看

ReentrantLock (重入锁) 源码浅析

一.ReentrantLock简介ReentrantLock重入锁,顾名思义,就是支持重入的锁,它表示能够支持一个线程对资源的重复加锁:我们之前学习过Synchronized锁,它也是支持重入的一种锁,参考我的另一篇Synchronized 锁的实现原理与应用,Synchronized支持隐式的重入锁,比如递归方法,在方法运行时,执行线程在获取到了锁之后仍能连续多次地获取锁:ReentrantLock虽然不能隐式重入,但是获取到锁的线程多次调用lock方法,不会阻塞进入同步队列:除此之外在获取锁

ReentrantLock 重入锁

一.Lock接口: 在Java SE 5之后,并发包中新增了Lock接口及相关实现类来实现锁功能. Lock接口和synchronized关键字实现锁的区别: (1)Lock接口需要显示的获取和释放锁,sychronized是隐式的获取和释放锁.也正因为如此,使得Lock接口拥有了锁获取与释放的可操作性.可中断的获取锁.超时获取锁 等多种synchronized关键字所不具备的同步特性. 例如:一个场景,先获取锁A,然后获取锁B,当B获得后,同时释放A,同时获取锁C.以此类推.这种场景synch

java多线程---重入锁ReentrantLock

1.定义 重入锁ReentrantLock,支持重入的锁,表示一个线程对资源的重复加锁. 2.底层实现 每个锁关联一个线程持有者和计数器,当计数器为0时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法:成功后,JVM会记下锁的持有线程,并且将计数器置为1:此时其它线程请求该锁,则必须等待:而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增:当线程退出同步代码块时,计数器会递减,如果计数器为0,则释放该锁. 3.使用样例 eg: import java

JAVA多线程重入锁ReentrantLock应用

package concurrent; import java.util.concurrent.*; import java.util.concurrent.locks.ReentrantLock; /** * @Auther:zhl * @Date:2019/7/13 * @Description: 并发测试,重入锁ReentrantLock解决并发问题 */ public class ConcurrentSample { //并发线程数量 private static int users =

ReentrantLock 重入锁(下)

前沿: ReentrantLock 是java重入锁一种实现,在java中我们通常使用ReentrantLock 和 synchronized来实现锁功能,本篇通过例子来理解下ReentrantLock使用以及什么是可重入锁. 理解可重入: 1. 锁机制是为了多线程并发访问共享资源情况下为保证线程的安全,和对资源的原子性操作, 举个例子: i=i+1;其实是三部操作首先将i读取到线程的临时区存放,然后加一操作,最后将结果写回去.所谓锁机制就是保证一段程序在某段时间只有一个线程执行. 2. 可重入

Java可重入锁

锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) .这些已经写好提供的锁为我们开发提供了便利,但是锁的具体性质以及类型却很少被提及.本系列文章将分析JAVA下常见的锁名称以及特性,为大家答疑解惑. 四.可重入锁: 本文里面讲的是广义上的可重入锁,而不是单指JAVA下的ReentrantLock. 可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响. 在JAV

Java 多线程 重入锁

作为关键字synchronized的替代品(或者说是增强版),重入锁是synchronized的功能扩展.在JDK 1.5的早期版本中,重入锁的性能远远好于synchronized,但从JDK 1.6开始,JDK优化了synchronized,使两者性能差距不大.重入锁使用java.util.concurrent.locks.ReentrantLock类来实现. 使用重入锁可以指定何时加锁和何时释放锁,对逻辑控制的灵活性远远好于synchronized,退出临界区时必须释放锁.之所以称为重入锁,

ReentrantLock重入锁

上次博客讲到了通过wait()方法和notify()方法来实现循环打印数字和字母得问题.其实使用重入锁也可以实现同样得功能,那么开始我们先通过源码来了解一下重入锁把. public void lock() { sync.lock(); } 首先它有一个lock()方法,它用来加锁,从代码中可以看到,它调用得是sync.lock()方法, public class ReentrantLock implements Lock, java.io.Serializable { private stati