什么是死锁
在使用多线程以及多进程时,两个或两个以上的运算单元(进程、线程或协程),各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,就称为死锁
下面看个简单的例子:
public class DeadLockTest { public static void main(String[] args) { Object lock1 = new Object(); // 锁1 Object lock2 = new Object(); // 锁2 Thread t1 = new Thread(new Runnable() { @Override public void run() { // 先获取锁1 synchronized (lock1) { System.out.println("Thread 1:获取到锁1!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //获取锁2 System.out.println("Thread 1:等待获取2..."); synchronized (lock2) { System.out.println("Thread 1:获取到锁2!"); } } } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { // 先获取锁2 synchronized (lock2) { System.out.println("Thread 2:获取到锁2!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 获取锁1 System.out.println("Thread 2:等待获取1..."); synchronized (lock1) { System.out.println("Thread 2:获取到锁1!"); } } } }); t2.start(); } }
死锁产生原因
死锁只有同时满足以下四个条件才会发生:
互斥条件:线程对所分配到的资源具有排它性,在某个时间内锁资源只能被一个线程占用。如下图:
线程1已经持有的资源,不能再同时被线程2持有,如果线程2请求获取线程1已经占用的资源,那线程2只能等待,直到线程1释放资源。如下图:
持有并请求条件:
持有并请求条件是指,当线程1已经持有了资源1,又想申请资源2,而资源2已经被线程3持有了,所以线程1就会处于等待状态,但是线程1在等待资源2的同时并不会释放已经持有的资源1。
不可剥夺条件:当线程已经持有了资源 ,在未使用完之前,不能被剥夺。如下图:
线程2如果也想使用此资源,只能等待线程1使用完并释放后才能获取。
环路等待条件:在死锁发生的时候,两个线程获取资源的顺序构成了环形链,如下图:
线程1已经持有资源2,想获取资源1, 线程2已经获取了资源1,想请求资源2,这就形成资源请求等待的环形图。
死锁的排查
1.jstack
用法如下:
/opt/java8/bin/jstack Usage: jstack [-l] <pid> (to connect to running process) 连接活动线程 jstack -F [-m] [-l] <pid> (to connect to a hung process) 连接阻塞线程 jstack [-m] [-l] <executable> <core> (to connect to a core file) 连接dump的文件 jstack [-m] [-l] [server_id@]<remote server IP or hostname> (to connect to a remote debug server) 连接远程服务器 Options: -F to force a thread dump. Use when jstack <pid> does not respond (process is hung) -m to print both java and native frames (mixed mode) -l long listing. Prints additional information about locks -h or -help to print this help message
死锁日志如下:
"mythread2" #12 prio=5 os_prio=0 tid=0x0000000058ef7800 nid=0x1ab4 waiting on condition [0x0000000059f8f000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000d602d610> (a java.util.concurrent.lock s.ReentrantLock$NonfairSync) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt errupt(AbstractQueuedSynchronizer.java:836) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(A bstractQueuedSynchronizer.java:870) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Abstrac tQueuedSynchronizer.java:1199) at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLo ck.java:209) at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285) at DeathLock$2.run(DeathLock.java:34) Locked ownable synchronizers: - <0x00000000d602d640> (a java.util.concurrent.locks.ReentrantLock$Nonfa irSync) "mythread1" #11 prio=5 os_prio=0 tid=0x0000000058ef7000 nid=0x3e68 waiting on condition [0x000000005947f000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000d602d640> (a java.util.concurrent.lock s.ReentrantLock$NonfairSync) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt errupt(AbstractQueuedSynchronizer.java:836) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(A bstractQueuedSynchronizer.java:870) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Abstrac tQueuedSynchronizer.java:1199) at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLo ck.java:209) at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285) at DeathLock$1.run(DeathLock.java:22) Locked ownable synchronizers: - <0x00000000d602d610> (a java.util.concurrent.locks.ReentrantLock$Nonfa irSync) Found one Java-level deadlock: ============================= "mythread2": waiting for ownable synchronizer 0x00000000d602d610, (a java.util.concurrent.l ocks.ReentrantLock$NonfairSync), which is held by "mythread1" "mythread1": waiting for ownable synchronizer 0x00000000d602d640, (a java.util.concurrent.l ocks.ReentrantLock$NonfairSync), which is held by "mythread2" Java stack information for the threads listed above: =================================================== "mythread2": at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000d602d610> (a java.util.concurrent.lock s.ReentrantLock$NonfairSync) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt errupt(AbstractQueuedSynchronizer.java:836) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(A bstractQueuedSynchronizer.java:870) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Abstrac tQueuedSynchronizer.java:1199) at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLo ck.java:209) at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285) at DeathLock$2.run(DeathLock.java:34) "mythread1": at sun.misc.Unsafe.park(Native Method) - parking to wait for <0x00000000d602d640> (a java.util.concurrent.lock s.ReentrantLock$NonfairSync) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt errupt(AbstractQueuedSynchronizer.java:836) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(A bstractQueuedSynchronizer.java:870) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Abstrac tQueuedSynchronizer.java:1199) at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLo ck.java:209) at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285) at DeathLock$1.run(DeathLock.java:22) Found 1 deadlock.
可以根据日志输出来直接定位到具体的死锁代码。
2.jmc
jmc是Oracle Java Mission Control的缩写,是一个对Java程序进行管理、监控、概要分析和故障排查的工具套件。在JDK的bin目录中,同样是双击启动,如下图所示:
jmc打开如下:
需要选择死锁类,右键启动JMX控制台,然后就可以发现死锁和死锁具体信息。
常用工具还有jconsole,jvisualvm等不再一一介绍,感兴趣的可以自已查看一下。
避免死锁问题的发生
死锁分析
还要看死锁产生的4个条件,只要不满足条件就无法产生死锁了,互斥条件和不可剥夺条件是系统特性无法阻止。只能通过破坏请求和保持条件或者是环路等待条件,从而来解决死锁的问题。
避免死锁的方案
一、固定加锁顺序
即通过有顺序的获取锁,从而避免产生环路等待条件,从而解决死锁问题的。来看环路等待的例子:
线程1先获取了锁A,再请求获取锁B,线程2与线程1同时执行,线程2先获取锁B,再请求获取锁A,两个线程都先占用了各自的资源(锁A和锁B)之后,再尝试获取对方的锁,从而造成了环路等待问题,最后造成了死锁。
此时只需将线程1和线程2获取锁的顺序进行统一,也就是线程1和线程2同时执行之后,都先获取锁A,再获取锁B。因为只有一个线程能成功获取到锁A,没有获取到锁A的线程就会等待先获取锁A,此时得到锁A的线程继续获取锁 B,因为没有线程争抢和拥有锁B,那么得到锁A的线程就会顺利的拥有锁B,之后执行相应的代码再将锁资源全部释放,然后另一个等待获取锁A的线程就可以成功获取到锁资源,这样就不会出现死锁的问题了。
二、开放调用避免死锁
在协作对象之间发生死锁的场景中,主要是因为在调用某个方法时就需要持有锁,并且在方法内部也调用了其他带锁的方法,如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用,同步代码块最好仅被用于保护那些涉及共享状态的操作。
三、使用定时锁
使用显式Lock锁,在获取锁时使用tryLock()方法。当等待超过时限的时候,tryLock()不会一直等待,而是返回错误信息,能够有效避免死锁问题。
总结
简单来说,死锁问题的产生是由两个或者以上线程并行执行的时候,争夺资源而互相等待造成的。
死锁只有同时满足互斥、持有并等待、不可剥夺、环路等待这四个条件的时候才会发生。
所以要避免死锁问题,就是要破坏其中一个条件即可,最常用的方法就是使用资源有序分配法来破坏环路等待条件。