可重入锁(又名递归锁)
可重入锁是指指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁 (前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
如果是 1 个有 synchronized 修饰的递归调用方法,程序第 2 次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。
所以 Java 中 ReentrantLock 和 synchronized 都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
可重入锁种类
- 隐式锁 Synchronized synchronized 是 java 中的关键字,默认是可重入锁,即隐式锁
在同步块中
public class ReEntryLockDemo {
public static void main(String[] args) {
final Object objectLockA = new Object();
new Thread(() -> {
synchronized (objectLockA) {
System.out.println("-----外层调用");
synchronized (objectLockA) {
System.out.println("-----中层调用");
synchronized (objectLockA) {
System.out.println("-----内层调用");
}
}
}
}, "a").start();
}
}
//-----外层调用
//-----中层调用
//-----内层调用
在同步方法中
public class ReEntryLockDemo {
public synchronized void m1() {
// 指的是可重复可递归调用的锁,在外层使用之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁
System.out.println(Thread.currentThread().getName() + "\t" + "-----come in m1");
m2();
System.out.println(Thread.currentThread().getName() + "\t-----end m1");
}
public synchronized void m2() {
System.out.println("-----m2");
m3();
}
public synchronized void m3() {
System.out.println("-----m3");
}
public static void main(String[] args) {
ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
reEntryLockDemo.m1();
}
}
/**
* main -----come in m1
* -----m2
* -----m3
* main -----end m1
*/
Synchronized 的重入实现机理
ObjectMonitor.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;}
- ObjectMonitor.hpp 底层:每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。_count _owner
- 首次加锁:当执行 monitorenter 时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java 虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加 1。
- 重入:在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加 1,否则需要等待,直至持有线程释放该锁。
- 释放锁:当执行 monitorexit 时,Java 虚拟机则需将锁对象的计数器减 1。计数器为零代表锁已被释放
显式锁 Lock
显式锁(即 Lock)也有 ReentrantLock 这样的可重入锁
感觉所谓的显式隐式即是指显示/隐式的调用锁
注意:lock unlock 要成对
public class ReEntryLockDemo {
static Lock lock = new ReentrantLock();
public static void main(String[] args) {
{
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t----come in 外层调用");
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t------come in 内层调用");
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}, "t1").start();
}
}
}
//t1 ----come in 外层调用
//t1 ------come in 内层调用
假如 lock unlock 不成对,单线程情况下问题不大,但多线程下出问题
public class ReEntryLockDemo {
static Lock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t----come in 外层调用");
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t------come in 内层调用");
} finally {
lock.unlock();
}
} finally {
//lock.unlock();//-------------------------不成对 | 多线程情况
}
}, "t1").start();
new Thread(() -> {
lock.lock();
try {
System.out.println("t2 ----外层调用 lock");
} finally {
lock.unlock();
}
}, "t2").start();
}
}
//t1 ----come in 外层调用
//t1 ------come in 内层调用
//(t2 ----外层调用 lock 假如不成对,这句话就不显示了)
死锁及排查
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。 a 跟 b 两个资源互相请求对方的资源
死锁产生的原因
- 系统资源不足
- 进程运行推进的顺序不合适
- 资源分配不当
public class DeadLockDemo {
public static void main(String[] args) {
Object object1 = new Object();
Object object2 = new Object();
new Thread(() -> {
synchronized (object1) {
System.out.println(Thread.currentThread().getName() + "\t 持有 a 锁,想获得 b 锁");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//使得线程 b 也启动
synchronized (object2) {
System.out.println(Thread.currentThread().getName() + "\t 成功获得 b 锁");
}
}
}, "A").start();
new Thread(() -> {
synchronized (object2) {
System.out.println(Thread.currentThread().getName() + "\t 持有 b 锁,想获得 a 锁");
synchronized (object1) {
System.out.println(Thread.currentThread().getName() + "\t 成功获得 a 锁");
}
}
}, "B").start();
}
}
如何排查死锁
用命令
- jps -l 查看当前进程运行状况
- jstack 进程编号 查看该进程信息
小总结
指针指向 monitor 对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个 monitor 与之关联,当一个 monitor 被某个线程持有后,它便处于锁定状态。在 Java 虚拟机 (HotSpot) 中,monitor 是由 ObjectMonitor 实现的,其主要数据结构如下(位于 HotSpot 虚拟机源码 ObjectMonitor.hpp,C++ 实现的
