什么是死锁
简单说:
有一个线程A,按照先获取锁a再获得锁b的的顺序获得锁,
而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁,
这个时候因为两个线程都在等待彼此手里的锁而形成了死锁。
如图:
死锁产生的四个条件
- 互斥条件
进程持有的资源,保证同一时间内只能有一个线程持有。 - 不剥夺条件
进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。 - 请求和保持条件
进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此是请求阻塞,但是它又对自己获得的资源保持不放。 - 环路等待条件
若干进程间形成首尾相接循环等待资源的关系。
手写一个死锁
public class TestDeaLock {
private Lock lockA=new ReentrantLock();
private Lock lockB=new ReentrantLock();
public void method1(){
try {
lockA.lock();
//这里假设执行业务逻辑,需要执行1s,这个时间线程2获取到了lockB
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"获取A锁!开始尝试获取B锁.......");
lockB.lock();
System.out.println(Thread.currentThread().getName()+"获取B锁!");
} catch (Exception e) {
e.printStackTrace();
}finally {
lockA.unlock();
lockB.unlock();
}
}
public void method2(){
try {
lockB.lock();
System.out.println(Thread.currentThread().getName()+"获取B锁!开始尝试获取A锁.....");
lockA.lock();
System.out.println(Thread.currentThread().getName()+"获取A锁!");
} finally {
lockB.unlock();
lockA.unlock();
}
}
public static void main(String[] args) {
TestDeaLock tdl=new TestDeaLock();
new Thread(()->{
tdl.method1();
}).start();
new Thread(()->{
tdl.method2();
}).start();
}
}
运行结果:
Thread-1获取B锁!开始尝试获取A锁.....
Thread-0获取A锁!开始尝试获取B锁.......
怎么解决死锁
我们就拿上面的手写锁代码进行排查。
使用jps命令定位进程号
14000 sun.tools.jps.Jps
16672 org.jetbrains.jps.cmdline.Launcher
13076
29512 com.ws.TestDeaLock
4376
从进程号中可以看出运行程序的进程号:29512
使用jstack命令找到死锁查看
使用命令:jstack 29512
部分内容...
Found one Java-level deadlock:
=============================
"Thread-1":
waiting for ownable synchronizer 0x000000076b5042d8, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "Thread-0"
"Thread-0":
waiting for ownable synchronizer 0x000000076b504308, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076b5042d8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at com.ws.TestDeaLock.mothod2(TestDeaLock.java:41)
at com.ws.TestDeaLock.lambda$main$1(TestDeaLock.java:55)
at com.ws.TestDeaLock$$Lambda$2/1348949648.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
"Thread-0":
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076b504308> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at com.ws.TestDeaLock.mothod1(TestDeaLock.java:27)
at com.ws.TestDeaLock.lambda$main$0(TestDeaLock.java:52)
at com.ws.TestDeaLock$$Lambda$1/736709391.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
Found 1 deadlock.
从堆栈信息里可以查看出jvm已经帮我们找到了一个死锁:
线程Thread-1在等待获取0x000000076b5042d8,这个锁被Thread-0占有,
Thread-0在等待获取0x000000076b504308,这个锁被Thead-1占有,这就形成了环路等待,造成了死锁。
- 可以结束这个进程
怎么避免死锁
- 在开发时需要慎重使用锁,需要注意尽量不要在已经获取到锁的情况下,再去尝试获取其他的锁。
- 获取锁的时候可以使用超时放弃(lock提供的方法)
- 调整申请锁的顺序
比如上面的死锁,method2可以调整为:
public void method2(){
try {
//这里获取锁的顺序和method1的顺序相同就可以避免
lockA.lock();
System.out.println(Thread.currentThread().getName()+"获取A锁!");
lockB.lock();
System.out.println(Thread.currentThread().getName()+"获取B锁!开始尝试获取A锁.....");
} finally {
lockB.unlock();
lockA.unlock();
}
}