如何定位&预防死锁
什么是死锁?
简单来说就是并发环境下,两个或两个以上的线程互相等待资源,导致“永久阻塞”的现象
代码示例:
public class Main {private static Object resource1 = new Object();private static Object resource2 = new Object();public static void main(String[] args) {new Thread(() -> {synchronized (resource1) {System.out.println(Thread.currentThread().getName() + "获取resource1成功");try {Thread.sleep(3000);synchronized (resource2) {System.out.println(Thread.currentThread().getName() + "获取resource2成功");}} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("执行成功");}).start();new Thread(() -> {synchronized (resource2) {System.out.println(Thread.currentThread().getName() + "获取resource2成功");synchronized (resource1) {System.out.println(Thread.currentThread().getName() + "获取resource1成功");}}System.out.println("执行成功");}).start();}
}
运行结果
代码解读:
- 线程1先去获取到了resource1这个资源,睡眠了3000ms,可以理解为模拟一个重操作
- 线程2在线程1睡眠的期间获取到了resource2这个资源,接着去尝试获取resource1资源,但是resource1被线程1所占有,导致了线程2的阻塞等待
- 线程1在睡眠3000ms后,去尝试获取resource2资源,发现resource2已经被抢占了,于是也阻塞等待资源的释放
- 最后,线程1和线程2都阻塞住了,也就是发生了“死锁”
怎么去定位死锁?
- 通过jps命令查看pid
- 不关闭死锁进程,打开终端输入jstack 死锁进程对应的pid
一旦线程发生了死锁,我们在线上一般没有办法进行有效解决,只能去进行程序的重启,不然会导致越来越多的线程堆积,从而出现OOM
所以应对死锁最好的方法就是尽量避免让程序出现死锁🐶
产生死锁的四大因素:
- 互斥,共享资源同一时刻只能被一个线程占用
- 占有并等待:线程在占有资源后,继续尝试占有其他线程正在占有的资源,造成当前线程等待
- 不可抢占:线程占有的资源只能线程本身释放
- 循环等待:两个线程互相等待资源的释放
如何避免死锁?
其实破坏死锁就是破坏构造死锁的四大因素之一就可以了
破坏互斥?
互斥性一般是比较难以破坏的,很多资源确实在同一时刻只用一个线程才能使用
破坏占有并等待?
诶,这个还真可以,既然线程存在因为资源获取不到而发生的阻塞等待行为,那么在线程首次执行前就把所有的资源都申请了不就行了,就能保证肯定有一个线程能够正常执行,前提是本身系统资源足以让一个线程执行完成
破坏不可抢占?
这个也可以,如果当前线程申请不到所要的资源,可以手动释放手里的资源,可以使用实现Lock接口的类,例如ReentrantLock的tryLock方法,可以传入锁的超时释放时间,一旦超过传入的超时时间当前线程就会自动释放手中的资源
破坏循环等待条件?
将资源从小到大编号,线程请求资源只能先请求编号较小的资源,线程2不再先去请求resource2,而是先去请求resource1,再去请求resource2;这样就不会导致死锁
总结
通过以上三种方式确实可以破坏死锁,但是会极大的抑制系统的性能
避免死锁算法
银行家算法
银行家算法的实质就是**要设法保证系统动态分配资源后不进入不安全状态,以避免可能产生的死锁。**即没当进程提出资源请求且系统的资源能够满足该请求时,系统将判断满足此次资源请求后系统状态是否安全,如果判断结果为安全,则给该进程分配资源,否则不分配资源,申请资源的进程将阻塞。
这里就不介绍了,可以看银行家算法