PPXu

死锁

2018-10-21

“死锁”的含义

所谓死锁: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

Java代码举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class Deadlock {

private Object o1 = new Object();

private Object o2 = new Object();

public void lock1() {
// 获取o1对象锁
synchronized( o1 ) {
try {
System.out.println( "l1 lock o1" );
// 获取o1后先等一会儿,让Lock2有足够的时间锁住o2
Thread.sleep( 1000 );
// 接着获取o2对象锁
synchronized( o2 ) {
System.out.println( "l1 lock o2" );
}
} catch( Exception e ) {
e.printStackTrace();
}
}
}

public void lock2() {
// 获取o2对象锁
synchronized( o2 ) {
try {
System.out.println( "l2 lock o2" );
// 获取o2后先等一会儿,让Lock1有足够的时间锁住o1
Thread.sleep( 1000 );
// 接着获取o1对象锁
synchronized( o1 ) {
System.out.println( "l2 lock o1" );
}
} catch( Exception e ) {
e.printStackTrace();
}
}
}

public static void main( String[] args ) {
Deadlock lock = new Deadlock();

Thread t1 = new Thread( new Runnable() {

@Override
public void run() {
lock.lock1();
}
} );

Thread t2 = new Thread( new Runnable() {

@Override
public void run() {
lock.lock2();
}
} );

t1.start();
t2.start();
}
}

创建两个对象,两条线程,用synchronized锁住对象,线程1先锁对象1后锁对象2,线程2先锁对象2后锁对象1。假设线程1先锁对象1,然后休眠1秒,线程锁对象2,之后线程1就没法锁对象2,线程2也没法锁住对象1,双方都在等待对方释放自己在等待的锁。

“死锁”产生的原因及四个必要条件

“死锁”的原因可归结为

  1. 竞争资源。当系统中供多个进程共享的资源如打印机、公用队列等,其数目不足以满足进程的需要时,会引起诸进程的竞争而产生死锁。

  2. 进程间推进顺序非法。进程在运行过程中,请求和释放资源的顺序不当,也同样会导致产生进程死锁。

产生“死锁”的四个必要条件

  • 互斥(Mutual exclusion):存在这样一种资源,它在某个时刻只能被分配给一个执行绪(也称为线程)使用;

  • 持有(Hold and wait):当请求的资源已被占用从而导致执行绪阻塞时,资源占用者不但没有释放该资源,而且还可以继续请求更多资源;

  • 不可剥夺(No preemption):执行绪获得到的互斥资源不可被强行剥夺,换句话说,只有资源占用者自己才能释放资源;

  • 环形等待(Circular wait):若干执行绪以不同的次序获取互斥资源,从而形成环形等待的局面,想象在由多个执行绪组成的环形链中,每个执行绪都在等待下一个执行绪释放它持有的资源。

结合代码例子理解“死锁”的产生

  • 互斥:两条线程各自占有的锁
  • 持有:线程1持有线程2想要获得的锁1,线程2持有线程1想要的锁2,双方都没有 释放各自占有的对象锁,并且继续请求对方占有的锁
  • 不可剥夺:两条线程得到互斥资源都没法被强行剥夺
  • 环形等待:T1{O1}→→T2{O2}→→T1{O1},{}表示被左边的线程占有{}里的资源,→→表示左边线程申请(等待)右边线程释放其占有的资源
    PS:环形等待可以是多个线程对多个资源的争夺

“死锁”问题定位

获取java进程ID

1
ps aux | grep "java"

用jstack看进程堆栈

1
2
# 替换进程ID(pid)
jstack -l {pid} | grep -A50 -B10 "deadlock"

Java线程间死锁堆栈

“死锁”的预防和解除

理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁,消除产生死锁的四个必要条件中的任何一个都可以预防和解除死锁。不难看出,在死锁的四个必要条件中,第二、三和四项条件比较容易消除。

  1. 静态分配:采用资源静态分配策略(进程资源静态分配方式是指一个进程在建立时就分配了它需要的全部资源),破坏”部分分配”条件;

  2. 可剥夺:允许进程剥夺使用其他进程占有的资源,从而破坏”不可剥夺”条件;

  3. 有序分配:采用资源有序分配法,破坏”环路”条件

参考资料

“死锁”四个必要条件的合理解释



扫描二维码,分享此文章