什么是死锁
死锁是多线程编程中会遇到的一个问题,下面就先来讲一下什么是死锁。
首先,大家都知道的是,当一个线程持有一个锁的时候,其他尝试获取这个锁的线程都会阻塞。
那么如果,线程A持有了锁A,并且尝试获取锁B,线程B持有了锁B,并且尝试获取锁A。那么这两个线程将会永远阻塞下去,这就是最简单的死锁。
示例
下面给出简单的死锁代码实现:
public class DeadLockTest {
public static void main(String[] args) {
DeadLock deadLock = new DeadLock();
new Thread(new Runnable() {
@Override
public void run() {
deadLock.methodA();
}
}, "Thread-A").start();
new Thread(new Runnable() {
@Override
public void run() {
deadLock.methodB();
}
}, "Thread-B").start();
}
}
class DeadLock {
private final Object lockA = new Object();
private final Object lockB = new Object();
public void methodA() {
synchronized (lockA) {
System.out.println("methodA in");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println("methodA out");
}
}
}
public void methodB() {
synchronized (lockB) {
System.out.println("methodB in");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockA) {
System.out.println("methodB out");
}
}
}
}
可以注意的是,由于线程抢占CPU的不确定性,所以我们在代码中调用Thread.sleep(1000)让线程停了一秒钟。
上面代码执行后控制台输出:
- 由于程序一直没有停止,所以程序肯定进入了死锁
- 由于methodA和methodB都只打印”in”而没有打印”out”,所以Thread-A和Thread-B分别在methodA和methodB中阻塞了
jstack分析死锁
下面我们再使用jstack打印堆栈信息来确认一下是否发生死锁
- 1.使用jps获取main方法Java虚拟机pid
C:\Users\lebron>jps
5360 Jps
5820
8188 DeadLockTest
- 2.jstack打印堆栈信息
C:\Users\lebron>jstack 8188
2018-07-24 16:21:24
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.161-b12 mixed mode):
Found one Java-level deadlock:
=============================
"Thread-B":
waiting to lock monitor 0x00000000177997b8 (object 0x00000000d6adf598, a java.util.ArrayList),
which is held by "Thread-A"
"Thread-A":
waiting to lock monitor 0x0000000017795718 (object 0x00000000d6adf750, a java.util.HashSet),
which is held by "Thread-B"
Java stack information for the threads listed above:
===================================================
"Thread-B":
at com.tvunetworks.wedding.device.DeadLock.methodB(DeadLockTest.java:55)
- waiting to lock <0x00000000d6adf598> (a java.util.ArrayList)
- locked <0x00000000d6adf750> (a java.util.HashSet)
at com.tvunetworks.wedding.device.DeadLockTest$2.run(DeadLockTest.java:21)
at java.lang.Thread.run(Thread.java:748)
"Thread-A":
at com.tvunetworks.wedding.device.DeadLock.methodA(DeadLockTest.java:41)
- waiting to lock <0x00000000d6adf750> (a java.util.HashSet)
- locked <0x00000000d6adf598> (a java.util.ArrayList)
at com.tvunetworks.wedding.device.DeadLockTest$1.run(DeadLockTest.java:14)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
这里只截取了部分jstack信息,通过堆栈信息,可以很明确的看到Thread-B在尝试获取锁A,Thread-A在尝试获取锁B,并且发生了一个死锁。
死锁的危害
- 导致程序得不到正确的结果:因为程序产生思索,发生阻塞,不会继续向下执行
- 浪费资源:产生死锁,最少有两个线程会发生阻塞
- 产生新的死锁:产生死锁的线程会一直占有着锁资源,会导致其他尝试获取该锁的线程也发生死锁,产生多米诺效应
规避死锁
既然产生死锁的危害这么大,那我们要怎么规避死锁呢
- 方法开始前一次性申请所需要的资源
- 当线程申请新的资源不被满足的时候,释放已占有的资源,下次需要时在重新申请
- 对系统中需要申请的资源进行线性排序,规定加锁顺序
喜欢文章的,扫码关注微信公众号