定义
可重入锁,又叫递归锁。是指同一个线程在外层方法获得锁的时候,再进入该线程的内层方法会自动获取锁(前提:锁对象是同一个对象)。
Java中ReentrantLock和synchornized 都是可重入锁,可重入锁的一个优点可一定程度避免死锁。
即:可重复进入的同步锁。
可重入锁的种类
- 隐式锁:Synchorized关键字
- 显示锁:Lock,也有ReentrantLock这样的可重入锁
Synchronized可重入的实现机制
每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。
当执行monitorenter时,如果目标锁对象的计数器为0,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象二点持有线程设置为当前线程,并且将计数器加1.
在目标锁的计数器不为0的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将计数器加1,否则需要等待,直至持有线程释放该锁。
当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1.计数器为0代表锁已被释放。线程阻塞唤醒的三种方式
Object wait和notify
- wait和notify必须在Synchronized代码块中使用,且成对出现
-
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()
底层就是UnSafe类。所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,然后会将permit再次设置为0并返回。unpark()
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) {
Thread a = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " come in ");
// 被阻塞,通过需要许可证
LockSupport.park();
System.out.println(Thread.currentThread().getName() + " notified ");
}, "a");
a.start();
Thread b = new Thread(() -> {
LockSupport.unpark(a);
System.out.println(Thread.currentThread().getName() + " notify A ");
}, "b");
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方法时,
- 为什么可以先唤醒线程后阻塞线程
因为unpark获得了一个凭证,之后再调用park()方法,就可以名正言顺的凭证消费,故不会阻塞。
- 为什么唤醒两次后阻塞两次,但最终结果还是会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark和调用一个unpark效果一样,只会增加一个凭证。而调用两次park()却需要消费两个凭证,证不够,不能放行。