一、什么是死锁
java中的死锁是一种编程情况,其中两个或多个线程被永久阻塞,Java死锁情况出现至少两个线程和两个或更多资源。
在这里,我们将写了一个简单的程序,它将导致java死锁场景,然后我们将分析它。
package com.sukang.sort;
/**
* @description:
* @author: sukang
* @date: 2020-11-09 15:48
*/
public class ThreadDeadlock {
public static void main(String[] args) {
Object obj1 = new Object();
Object obj2 = new Object();
Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
Thread t2 = new Thread(new SyncThread(obj2, obj1), "t2");
t1.start();
t2.start();
}
}
class SyncThread implements Runnable{
private Object obj1;
private Object obj2;
public SyncThread(Object obj1, Object obj2) {
this.obj1 = obj1;
this.obj2 = obj2;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + " acquiring lock on " + obj1);
synchronized (obj1) {
System.out.println(name + " acquired lock on " + obj1);
work();
System.out.println(name + " acquiring lock on " + obj2);
synchronized (obj2) {
System.out.println(name + " acquired lock on "+obj2);
work();
}
System.out.println(name + " released lock on "+obj2);
}
System.out.println(name + " released lock on "+obj1);
System.out.println(name + " finished execution.");
}
private void work(){
try {
Thread.sleep(3000);
} catch ( InterruptedException e ) {
e.printStackTrace();
}
}
}
测试结果
可以看到每个线程中都能成功获取第一个对象的锁,最终却阻塞在了获取第二个对象,造成了线程之间的互相等待,形成了死锁
二、怎么排查死锁
方法很多,我们就用jstack方法去排查
首先通过jps命令获取进程号
通过 jstack -l 进程号 得到结果
C:\Users\sukang>jstack -l 1792
2020-11-09 16:38:35
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.181-b13 mixed mode):
"JMX server connection timeout 25" #25 daemon prio=5 os_prio=0 tid=0x000000001a749800 nid=0x18bc in Object.wait() [0x000000000159f000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at com.sun.jmx.remote.internal.ServerCommunicatorAdmin$Timeout.run(ServerCommunicatorAdmin.java:168)
- locked <0x00000000d7d80720> (a [I)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
"RMI Scheduler(0)" #18 daemon prio=5 os_prio=0 tid=0x000000001b881000 nid=0x3424 waiting on condition [0x000000001c14f000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000d7d88838> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
"RMI TCP Accept-0" #16 daemon prio=5 os_prio=0 tid=0x000000001b7b2000 nid=0x2840 runnable [0x000000001bf4e000]
java.lang.Thread.State: RUNNABLE
at java.net.DualStackPlainSocketImpl.accept0(Native Method)
at java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:131)
at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:199)
- locked <0x00000000d7d982b0> (a java.net.SocksSocketImpl)
at java.net.ServerSocket.implAccept(ServerSocket.java:545)
at java.net.ServerSocket.accept(ServerSocket.java:513)
at sun.management.jmxremote.LocalRMIServerSocketFactory$1.accept(LocalRMIServerSocketFactory.java:52)
at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.executeAcceptLoop(TCPTransport.java:405)
at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.run(TCPTransport.java:377)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x00000000036b2800 nid=0x35a0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"t2" #13 prio=5 os_prio=0 tid=0x000000001a7f8800 nid=0x2c38 waiting for monitor entry [0x000000001b64f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.sukang.sort.SyncThread.run(ThreadDeadlock.java:39)
- waiting to lock <0x00000000d7d88aa8> (a java.lang.Object)
- locked <0x00000000d7d88a98> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
"t1" #12 prio=5 os_prio=0 tid=0x000000001a7f8000 nid=0x4cfc waiting for monitor entry [0x000000001b54f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.sukang.sort.SyncThread.run(ThreadDeadlock.java:39)
- waiting to lock <0x00000000d7d88a98> (a java.lang.Object)
- locked <0x00000000d7d88aa8> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000000001a7da000 nid=0x1828 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x000000001a74b800 nid=0x1cec waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000000001a738000 nid=0x34c0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000000001a735000 nid=0x3c7c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000000001a71e800 nid=0x11c8 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001a734000 nid=0x3d1c runnable [0x000000001ae4e000]
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 <0x00000000d7db63e8> (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 <0x00000000d7db63e8> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)
Locked ownable synchronizers:
- None
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001a68a800 nid=0xa40 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001a6e0000 nid=0x4340 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001a670800 nid=0x5d8 in Object.wait() [0x000000001ab4f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x00000000d7d98880> (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)
Locked ownable synchronizers:
- None
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000000001875d000 nid=0x45e4 in Object.wait() [0x000000001a64f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x00000000d7da82f0> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
Locked ownable synchronizers:
- None
"VM Thread" os_prio=2 tid=0x0000000018758800 nid=0x3d34 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000000036c9000 nid=0x1c60 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000000036ca800 nid=0x31c0 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000000036cc000 nid=0x4408 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000000036cd800 nid=0x22a4 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x00000000036cf800 nid=0x1040 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x00000000036d1000 nid=0x3854 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x00000000036d4000 nid=0x4650 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x00000000036d6000 nid=0x35f0 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x000000001a7ef000 nid=0x5700 waiting on condition
JNI global references: 229
Found one Java-level deadlock:
=============================
"t2":
waiting to lock monitor 0x0000000018760b98 (object 0x00000000d7d88aa8, a java.lang.Object),
which is held by "t1"
"t1":
waiting to lock monitor 0x00000000187634d8 (object 0x00000000d7d88a98, a java.lang.Object),
which is held by "t2"
Java stack information for the threads listed above:
===================================================
"t2":
at com.sukang.sort.SyncThread.run(ThreadDeadlock.java:39)
- waiting to lock <0x00000000d7d88aa8> (a java.lang.Object)
- locked <0x00000000d7d88a98> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
"t1":
at com.sukang.sort.SyncThread.run(ThreadDeadlock.java:39)
- waiting to lock <0x00000000d7d88a98> (a java.lang.Object)
- locked <0x00000000d7d88aa8> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
三、死锁产生的4个必要条件
1、互斥: 某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
2、占有且等待: 一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3、不可抢占: 别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4、循环等待: 存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。
当以上四个条件均满足,必然会造成死锁,发生死锁的进程无法进行下去,它们所持有的资源也无法释放。这样会导致CPU的吞吐量下降。所以死锁情况是会浪费系统资源和影响计算机的使用性能的。那么,解决死锁问题就是相当有必要的了。
四、避免死锁的方法
1、死锁预防 ----- 确保系统永远不会进入死锁状态
产生死锁需要四个条件,那么,只要这四个条件中至少有一个条件得不到满足,就不可能发生死锁了。由于互斥条件是非共享资源所必须的,不仅不能改变,还应加以保证,所以,主要是破坏产生死锁的其他三个条件。
a、破坏“占有且等待”条件
方法1:所有的进程在开始运行之前,必须一次性地申请其在整个运行过程中所需要的全部资源。
优点:简单易实施且安全。
缺点:因为某项资源不满足,进程无法启动,而其他已经满足了的资源也不会得到利用,严重降低了资源的利用率,造成资源浪费。使进程经常发生饥饿现象。
方法2:该方法是对第一种方法的改进,允许进程只获得运行初期需要的资源,便开始运行,在运行过程中逐步释放掉分配到的已经使用完毕的资源,然后再去请求新的资源。这样的话,资源的利用率会得到提高,也会减少进程的饥饿问题。
b、破坏“不可抢占”条件
当一个已经持有了一些资源的进程在提出新的资源请求没有得到满足时,它必须释放已经保持的所有资源,待以后需要使用的时候再重新申请。这就意味着进程已占有的资源会被短暂地释放或者说是被抢占了。
该种方法实现起来比较复杂,且代价也比较大。释放已经保持的资源很有可能会导致进程之前的工作实效等,反复的申请和释放资源会导致进程的执行被无限的推迟,这不仅会延长进程的周转周期,还会影响系统的吞吐量。
c、破坏“循环等待”条件
可以通过定义资源类型的线性顺序来预防,可将每个资源编号,当一个进程占有编号为i的资源时,那么它下一次申请资源只能申请编号大于i的资源。如图所示:
这样虽然避免了循环等待,但是这种方法是比较低效的,资源的执行速度回变慢,并且可能在没有必要的情况下拒绝资源的访问,比如说,进程c想要申请资源1,如果资源1并没有被其他进程占有,此时将它分配个进程c是没有问题的,但是为了避免产生循环等待,该申请会被拒绝,这样就降低了资源的利用率
2、避免死锁 ----- 在使用前进行判断,只允许不会产生死锁的进程申请资源
的死锁避免是利用额外的检验信息,在分配资源时判断是否会出现死锁,只在不会出现死锁的情况下才分配资源。
两种避免办法:
1、如果一个进程的请求会导致死锁,则不启动该进程
2、如果一个进程的增加资源请求会导致死锁 ,则拒绝该申请。
避免死锁的具体实现通常利用银行家算法
银行家算法
a、银行家算法的相关数据结构
可利用资源向量Available:用于表示系统里边各种资源剩余的数目。由于系统里边拥有的资源通常都是有很多种(假设有m种),所以,我们用一个有m个元素的数组来表示各种资源。数组元素的初始值为系统里边所配置的该类全部可用资源的数目,其数值随着该类资源的分配与回收动态地改变。
最大需求矩阵Max:用于表示各个进程对各种资源的额最大需求量。进程可能会有很多个(假设为n个),那么,我们就可以用一个nxm的矩阵来表示各个进程多各种资源的最大需求量
分配矩阵Allocation:顾名思义,就是用于表示已经分配给各个进程的各种资源的数目。也是一个nxm的矩阵。
需求矩阵Need:用于表示进程仍然需要的资源数目,用一个nxm的矩阵表示。系统可能没法一下就满足了某个进程的最大需求(通常进程对资源的最大需求也是只它在整个运行周期中需要的资源数目,并不是每一个时刻都需要这么多),于是,为了进程的执行能够向前推进,通常,系统会先分配个进程一部分资源保证进程能够执行起来。那么,进程的最大需求减去已经分配给进程的数目,就得到了进程仍然需要的资源数目了。
银行家算法通过对进程需求、占有和系统拥有资源的实时统计,确保系统在分配给进程资源不会造成死锁才会给与分配。
死锁避免的优点:不需要死锁预防中的抢占和重新运行进程,并且比死锁预防的限制要少。
死锁避免的限制:
必须事先声明每个进程请求的最大资源量
考虑的进程必须无关的,也就是说,它们执行的顺序必须没有任何同步要求的限制
分配的资源数目必须是固定的。
在占有资源时,进程不能退出