定义

可重入锁,又叫递归锁。是指同一个线程在外层方法获得锁的时候,再进入该线程的内层方法会自动获取锁(前提:锁对象是同一个对象)。
Java中ReentrantLock和synchornized 都是可重入锁,可重入锁的一个优点可一定程度避免死锁。
即:可重复进入的同步锁。

可重入锁的种类

  • 隐式锁:Synchorized关键字
  • 显示锁:Lock,也有ReentrantLock这样的可重入锁

    Synchronized可重入的实现机制

    每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。
    当执行monitorenter时,如果目标锁对象的计数器为0,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象二点持有线程设置为当前线程,并且将计数器加1.
    在目标锁的计数器不为0的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将计数器加1,否则需要等待,直至持有线程释放该锁。
    当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1.计数器为0代表锁已被释放。

    线程阻塞唤醒的三种方式

image.png

Object wait和notify

  • wait和notify必须在Synchronized代码块中使用,且成对出现
  • 必须先wait再notify,否则程序无法驱动执行

    Condition await和singal

  • await 和 singal 必须和Lock lock()、unlock()使用

  • 也存在顺序问题,必须先等待后唤醒

    LockSupport

    park(): 除非许可证可用,否则阻塞。
    LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(Permit),Permit只有两个值1和0,默认是0
    可以把许可看成是一种(0,1)信号量(Semaphore),但是和Semaphore不同的是,许可的累加上限是1;

  • 通知可以在阻塞之前执行

  • 无锁块要求

    LockSupport

    是什么?

    用于创建锁和其他同步类的基本线程阻塞原语。
    线程等待唤醒机制(wait、notify的改进加强版)
    LockSupport中的park() 和 unpark()的作用分类是阻塞线程和解除阻塞线程。

    阻塞和解除阻塞

    park()
    image.png
    底层就是UnSafe类。所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,然后会将permit再次设置为0并返回。
    unpark()
    image.png

    demo

    ```java package com.interview.demo.lock;

import java.util.concurrent.locks.LockSupport;

/**

  • @Author leijs
  • @date 2022/3/31 */ public class LockSupportDemo { public static void main(String[] args) {

    1. Thread a = new Thread(() -> {
    2. System.out.println(Thread.currentThread().getName() + " come in ");
    3. // 被阻塞,通过需要许可证
    4. LockSupport.park();
    5. System.out.println(Thread.currentThread().getName() + " notified ");
    6. }, "a");
    7. a.start();
    8. Thread b = new Thread(() -> {
    9. LockSupport.unpark(a);
    10. System.out.println(Thread.currentThread().getName() + " notify A ");
    11. }, "b");
    12. b.start();

    } }

```

重要说明

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport是一个线程阻塞工具类,所有的方法都是natvie方法,可以让线程在任意位置上阻塞,阻塞之后也有响应的唤醒方法,归根结底,LockSupport是调用的Unsafe的native方法。

LockSupport提供park()和uunpark()方法实现阻塞线程和解除线程阻塞的过程
LockSupport和每个使用它的线程都有一个许可(Permit)关联。permit相当于1 和 0 的开关,默认是0,调用一个unpark()就加1变成1.调用一个park()会消费permit,也就是从1变成0,同时park立即返回。
如再次调用park()会变成阻塞(因为permit为0了会阻塞在这里,一直到permit变为1,这个时候调用unpark()会把permit置为1).
每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark也不会积累凭证。

形象的理解:
线程阻塞需要消耗凭证(Permit),这个凭证最多只有一个。
当调用park方法时

  • 如果有凭证,则会直接消耗掉这个凭证然后正常退出
  • 如果无凭证给,则必须祖泽等待凭证可用。

当调用unpark方法时,

  • 它会增加一个凭证,但凭证最多只能有1个,累加无效’

    其他问题

  1. 为什么可以先唤醒线程后阻塞线程

因为unpark获得了一个凭证,之后再调用park()方法,就可以名正言顺的凭证消费,故不会阻塞。

  1. 为什么唤醒两次后阻塞两次,但最终结果还是会阻塞线程?

因为凭证的数量最多为1,连续调用两次unpark和调用一个unpark效果一样,只会增加一个凭证。而调用两次park()却需要消费两个凭证,证不够,不能放行。