本篇文章从Java线程、锁层面去考虑考虑死锁。
死锁:多个线程,彼此持有对方需要的锁资源,谁也不肯释放,谁也无法进行,而导致的僵死局面。比如:T1持有L1锁,需要L2锁才能运行,T2持有L2锁,需要L1锁才能够运行。
活锁:多个线程,彼此谦让对方需要的锁资源,谁也不能够拥有所资源,谁也没法进行的尴尬局面。比如:T1持有L1锁,发现没法获取到L2锁,立马释放L1,T2持有L2锁,发现没法获取L1锁,立马释放L2锁,如此反复。
死锁产生的四个条件
死锁产生需要依赖下面四个条件,缺一不可
- 互斥:共享资源只能被一个线程占用,如T1,T2
- 占有且保持:线程T1已占有L1锁资源,再去请求L2锁资源时,并不会释放L1资源。
- 不可抢占:其他线程不能争夺T1已经占用的资源。
- 循环等待:T1等待T2释放L2,T2等待T1释放L1
简单死锁示例
/**
* 死锁例子
* 线程L1 持有L1的情况下,尝试获取L2
* 线程L2 持有L2的情况下,尝试获取L1
*/
public class DeadLockDemo {
public static void main(String[] args) {
Object L1 = new Object();
Object L2 = new Object();
Thread T1 = new Thread(() -> {
while (true) {
synchronized (L1) {
synchronized (L2) {
//do something
}
}
}
});
Thread T2 = new Thread(() -> {
while (true) {
synchronized (L2) {
synchronized (L1) {
//do something
}
}
}
});
T1.start();
T2.start();
}
}
以上变量明明不值得参考
死锁排查
线程相关的排查比较简单,一般使用jps
和jstack
命令就可以搞定。
// 首先获取相关JVM的进程编号,可以看到目标JVM进程为6625
jps -l
6624 org.jetbrains.jps.cmdline.Launcher
6625 DeadLockDemo
6626 sun.tools.jps.Jps
4307 org.jetbrains.idea.maven.server.RemoteMavenServer36
382
// 利用jstack JVM进程号打印线程栈信息,可以看到连个相互等待
jstack 6625
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007f8f8a00eeb8 (object 0x000000076ac33cd8, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00007f8f8a0104b8 (object 0x000000076ac33ce8, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at DeadLockDemo.lambda$main$1(DeadLockDemo.java:26)
- waiting to lock <0x000000076ac33cd8> (a java.lang.Object)
- locked <0x000000076ac33ce8> (a java.lang.Object)
at DeadLockDemo$$Lambda$2/668386784.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at DeadLockDemo.lambda$main$0(DeadLockDemo.java:16)
- waiting to lock <0x000000076ac33ce8> (a java.lang.Object)
- locked <0x000000076ac33cd8> (a java.lang.Object)
at DeadLockDemo$$Lambda$1/381259350.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
其他工具如JConsole
也可以达到排查的效果,这里建议,大家在开发的过程中把线程重新起个名字,方便排查问题。
如何避免死锁
死锁产生需要四个条件同时满足,破坏任何一个条件,就可以避免死锁产生。第一个条件是在并发条件下,访问临界区域下必须的,因此不做考虑。
- 破坏占有且等待:一次性获取所有的锁资源。例如,上面的例子,可以用一个L1_2锁来代表L1,L2。只用锁一次
- 破坏不可抢占:当T1持有L1锁,但获取不到L2锁的时候,可以放弃L1锁。即尝试获取L2锁,超过时间没有获取到则T1放弃已占用的L1锁。但原生的
synchronized
的方式并不支持,需要用到JUC中的Lock
,尝试获取锁。一般尝试获得锁需要增加尝试时间,尝试时间需要做随机处理。否则可能发生,T1让了L1的同时T2让了L2,相互让锁,产生活锁现象。 - 破坏循环等待:让每个T1,T2获取锁的顺序保持一直,就可以避免相互之间的等待。
这里不得不提一下“银行家算法”,也是解决共享资源分配避免死锁的一种方案。推荐一篇博客,https://blog.csdn.net/qq_36260974/article/details/84404369 供大家参考。
网友评论