目录
可重入锁
是指在同一个线程在外层方法获取锁的时候,如果内部还有同步方法或者同步代码块,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。
所以Java中ReentrantLock和synchronized都是可重入锁,
可重入锁的一个优点是可一定程度避免死锁。
四个字分开来解释
可:可以
重:再次
入:进入
锁:同步锁
进入什么:进入同步域(即同步代码块/方法或显示锁锁定的代码)
一句话:一个线程中的多个流程可以获取同一把锁,持有这把锁可以再次进入。自己可以获取自己的内部锁。
可重入锁种类(隐式锁,显示锁)
隐式锁Synchronized
同步代码块中演示
public static void main(String[] args) {
final Object obj = new Object();
new Thread(()->{
synchronized (obj){
System.out.println(Thread.currentThread().getName()+"外层");
synchronized (obj){
System.out.println(Thread.currentThread().getName()+"中层");
synchronized (obj){
System.out.println(Thread.currentThread().getName()+"内层");
}
}
}
}).start();
}
三个同步代码块持有同一个锁——obj对象
Thread-0外层
Thread-0中层
Thread-0内层
同步方法中演示
public synchronized void m1() {
System.out.println("m1 come in...");
m2();
System.out.println("m1 end...");
}
private synchronized void m2() {
System.out.println("m2 come in...");
m3();
}
private synchronized void m3() {
System.out.println("m3 come in...");
}
public static void main(String[] args) {
ReentrantLockDemo reentrantLockDemo = new ReentrantLockDemo();
new Thread(()->{reentrantLockDemo.m1();},"t1").start();
}
三个同步方法持有同一个锁——当前类对象reentrantLockDemo
m1 come in...
m2 come in...
m3 come in...
m1 end...
Synchronized的重入实现机理
底层ObjectMoitor.hpp中记录
140行
ObjectMonitor() {
_header = NULL;
_count = 0; //用来记录该线程获取锁的次数
_waiters = 0,
_recursions = 0;//锁的重入次数
_object = NULL;
_owner = NULL; //------最重要的----指向持有ObjectMonitor对象的线程,记录哪个线程持有了我
_WaitSet = NULL; //存放处于wait状态的线程队列
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ;//存放处于等待锁block状态的线程队列
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
ObjectMoitor.hpp底层:每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。
_count //用来记录该线程获取锁的次数
_owner//------最重要的----指向持有ObjectMonitor对象的线程,记录哪个线程持有了我
- 当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。
- 在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
- 当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。
显式锁Lock
public class ObviousLock {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
new Thread(()->{
lock.lock();
try {
System.out.println("外层进入");
lock.lock();
try {
System.out.println("内层进入");
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}).start();
}
}
注意:lock
unlock
要成对使用
假如lock
unlock
不成对,单线程情况下问题不大,但多线程下出问题
public class ObviousLock {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
new Thread(()->{
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+" 外层进入");
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+" 内层进入");
} finally {
lock.unlock();
}
} finally {
// lock.unlock();
}
},"t1").start();
new Thread(()->{
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"外层进入");
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"内层进入");
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
},"t2").start();
}
}
//t1 外层进入
//t1 内层进入