什么是死锁?
所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程A,按照先锁1再获得锁2的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁2再锁1的顺序获得锁。如下图所示:
lock.png
我们可以在JAVA代码中模拟一下死锁的情况:
/**
* @author brianxia
* @version 1.0
* @date 2020/12/12 18:18
*/
public class DeadLock {
public static void main(String[] args) {
//创建两个资源文件用于加锁
Object obj1 = new Object();
Object obj2 = new Object();
new Thread(() -> {
//第一个线程,先拿到obj1,对obj1加锁
synchronized (obj1) {
try {
//休眠的作用是防止连续拿到两个资源
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2) {
}
}
}).start();
new Thread(() -> {
//第二个线程,先拿到obj2,对obj1加锁
synchronized (obj2) {
try {
//休眠的作用是防止连续拿到两个资源
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1) {
}
}
}).start();
}
}
上述代码模拟了最开始说明的情况:
- 线程A对obj1加锁
- 线程B对obj2加锁
- 线程A想要获取obj2的锁
- 线程B想要获取obj1的锁
双方互不让步,最终导致了死锁。
检测死锁
在JAVA中,可以使用jps+jstack命令检测死锁:
首先使用jps找到对应的进程ID,比如这个案例中,我的进程名字和类名是一致的DeadLock。
>jps
20512 QuorumPeerMain
10820 jar
15076 RemoteMavenServer36
22244 RemoteMavenServer36
14056 RemoteMavenServer36
21032 RemoteMavenServer36
26504
21036 RemoteMavenServer36
33712 DeadLock
34068 RemoteMavenServer36
21276 Launcher
30652 Jps
7196
接下来使用jstack分析线程运行情况:
jstack 33712
2020-12-12 18:21:06
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.181-b13 mixed mode):
"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x0000000002bbe800 nid=0x8e88 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Thread-1" #13 prio=5 os_prio=0 tid=0x000000001fc31800 nid=0x24a4 waiting for monitor entry [0x00000000205be000]
java.lang.Thread.State: BLOCKED (on object monitor)
at DeadLock.lambda$main$1(DeadLock.java:35)
- waiting to lock <0x000000076c112c80> (a java.lang.Object)
- locked <0x000000076c112c90> (a java.lang.Object)
at DeadLock$$Lambda$2/1096979270.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Thread-0" #12 prio=5 os_prio=0 tid=0x000000001fc2f800 nid=0x81b4 waiting for monitor entry [0x00000000204bf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at DeadLock.lambda$main$0(DeadLock.java:21)
- waiting to lock <0x000000076c112c90> (a java.lang.Object)
- locked <0x000000076c112c80> (a java.lang.Object)
at DeadLock$$Lambda$1/1324119927.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000000001e061000 nid=0x5ad4 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x000000001dfc8000 nid=0x8fd8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000000001dfba800 nid=0x3cc4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000000001dfb9800 nid=0x21bc waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000000001dfa3000 nid=0x4834 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001dfa6800 nid=0x6480 runnable [0x000000001f5be000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x000000076c24a6d8> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x000000076c24a6d8> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:61)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001df13000 nid=0x6698 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001df6a000 nid=0x872c runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001def3000 nid=0x3140 in Object.wait() [0x000000001f25f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076bf88ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x000000076bf88ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000000001c7fc800 nid=0x620 in Object.wait() [0x000000001f15e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076bf86bf8> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x000000076bf86bf8> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x000000001c7f7000 nid=0x2500 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002bd8800 nid=0x8f80 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002bda000 nid=0x2a5c runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002bdb800 nid=0x4b80 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002bdd000 nid=0x3b24 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000002be0800 nid=0x831c runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002be1800 nid=0x4330 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002be5000 nid=0x8654 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002be6000 nid=0x824c runnable
"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x0000000002be7000 nid=0x7214 runnable
"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x0000000002be8800 nid=0x8474 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x000000001e074000 nid=0x32e8 waiting on condition
JNI global references: 316
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x000000001c803628 (object 0x000000076c112c80, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x000000001c800d98 (object 0x000000076c112c90, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at DeadLock.lambda$main$1(DeadLock.java:35)
- waiting to lock <0x000000076c112c80> (a java.lang.Object)
- locked <0x000000076c112c90> (a java.lang.Object)
at DeadLock$$Lambda$2/1096979270.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at DeadLock.lambda$main$0(DeadLock.java:21)
- waiting to lock <0x000000076c112c90> (a java.lang.Object)
- locked <0x000000076c112c80> (a java.lang.Object)
at DeadLock$$Lambda$1/1324119927.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
重点来看最后一段话:
image.png
jstack命令已经发现了一个死锁,同时在下方标注了锁的ID。
- Thread-0拥有0x000000076c112c80锁,等待0x000000076c112c90锁。
- Thread-1拥有0x000000076c112c90锁,等待0x000000076c112c80锁。
这样就发生了死锁。
死锁产生的4个必要条件?
- 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
案例中线程1一直持有obj1的锁,线程2一直持有obj2的锁
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
线程均不释放锁
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
无法强制释放锁
- 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。
解决死锁的基本方法
- 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件) - 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
- 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
这里具体说一下资源有序分配法,这是原来的分配方式:
image.png
修改后的:
image.png
这样就不会产生死锁了。
/**
* @author brianxia
* @version 1.0
* @date 2020/12/12 18:18
*/
public class DeadLock {
public static void main(String[] args) {
//创建两个资源文件用于加锁
Object obj1 = new Object();
Object obj2 = new Object();
new Thread(() -> {
//第一个线程,先拿到obj1,对obj1加锁
synchronized (obj1) {
try {
//休眠的作用是防止连续拿到两个资源
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2) {
}
}
}).start();
new Thread(() -> {
//第二个线程,先拿到obj1,对obj2加锁,按照有序的方式解决死锁
synchronized (obj1) {
try {
//休眠的作用是防止连续拿到两个资源
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2) {
}
}
}).start();
}
}
当然还有一种方式是使用lock进行超时取消获取锁:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author brianxia
* @version 1.0
* @date 2020/12/12 18:18
*/
public class DeadLock2 {
public static void main(String[] args) {
//创建两个资源文件用于加锁
ReentrantLock reentrantLock1 = new ReentrantLock();
ReentrantLock reentrantLock2 = new ReentrantLock();
new Thread(() -> {
reentrantLock1.lock();
try {
//休眠的作用是防止连续拿到两个资源
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
if (reentrantLock2.tryLock(200, TimeUnit.MILLISECONDS)) {
System.out.println("线程1拿到锁2");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
reentrantLock1.unlock();
System.out.println("线程1解锁1");
}).start();
new Thread(() -> {
reentrantLock2.lock();
try {
//休眠的作用是防止连续拿到两个资源
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
if (reentrantLock1.tryLock(200, TimeUnit.MILLISECONDS)) {
System.out.println("线程2拿到锁1");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
reentrantLock2.unlock();
System.out.println("线程2解锁2");
}).start();
}
}
网友评论