死锁


synchronized块的乱用,会造成死锁。

下面来看看死锁形成的大致情况:

  • 线程T1,执行线程对象A
  • 线程T2,执行线程对象B
  • 线程对象A,里面有加锁的代码需要获得自己的锁,并且同时有另外一个加锁的代码需要获得线程对象B的锁。
  • 线程对象B,里面有加锁的代码需要获得自己的锁,并且同时有另外一个加锁的代码需要获得线程对象A的锁。
  • 简言之:
  • 线程对象A在获得自己的锁的时候,需要获得线程对象B的锁
  • 线程对象B在获得自己的锁的时候,需要获得线程对象A的锁
  • 那么这两个对象都在占用自己的锁的时候,想要获得对方对象的锁。
  • 因此,这两个对象都在等锁的池(lock pool)中,都处于等待拿锁的状态。
  • 所以死锁就形成了——两个对象会无限的等待下去。。。


来看看死锁示例代码:



DeadThreadA.java



package thread;

public class DeadThreadA implements Runnable {

public DeadThreadB deadThreadB; // 想拿到对方线程对象的锁,就得先获得引用

private int i = 0;
private int j = 0;

@Override
public void run() {

while (true) {
synchronized (this) { // 首先占用自己对象的锁
if (i >= 10)
break;

i++;
for (int j = 0; j < 10000000l; j++)
;
System.out.println(Thread.currentThread().getName() + ": i=" + i);

while (true) {
synchronized (deadThreadB) { // 在占用自己对象的锁的同时,需要获得deadThreadB的锁
if (j >= 10) {
break;
}
j++;
for (int j = 0; j < 10000000l; j++)
;
System.out.println(Thread.currentThread().getName() + ": j=" + j);
}
}

}

}

}

}


DeadThreadB.java




package thread;

public class DeadThreadB implements Runnable {

public DeadThreadA deadThreadA; // 想拿到对方线程对象的锁,就得先获得引用
private int i = 0;
private int j = 0;

@Override
public void run() {

while (true) {
synchronized (this) { // 首先占用自己对象的锁
if (i >= 10)
break;

i++;
for (int j = 0; j < 10000000l; j++)
;
System.out.println(Thread.currentThread().getName() + ": i= " + i);

while (true) {
synchronized (deadThreadA) { // 在占用自己对象的锁的同时,需要获得deadThreadA的锁
if (j >= 10) {
break;
}
j++;
for (int j = 0; j < 10000000l; j++)
;
System.out.println(Thread.currentThread().getName() + ": j= " + j);
}
}

}

}

}

}


代码的几点说明:



  • synchronized()块,只支持传入对象,如果想拿对方线程对象的锁,那么首先得获得对方线程对象的引用。
  • 当前的两个 线程对象,首先是各自已经占用了自己的锁,同时再,都尝试去拿对方的锁。
  • 所以,先synchronized (this),在占用自己的锁的同时,(没有释放自己的锁)再去拿对方的锁:synchronized (deadThreadB), synchronized (deadThreadA)
  • 也就是说,当前线程,要同时拿两个锁:首先是拿到自己锁,然后再去尝试拿对方的锁。

执行类,如下



DeadThreadMain.java



package thread;

public class DeadThreadMain {

public static void main(String ab[]) {
DeadThreadA a = new DeadThreadA();
DeadThreadB b = new DeadThreadB();

b.deadThreadA = a; // 获得对方线程对象的引用
a.deadThreadB = b; // 获得对方线程对象的引用

Thread t1 = new Thread(a);
Thread t2 = new Thread(b);

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

}
}




控制台就打印这么多,之后程序是一直运行的,但是就像死循环一样,永不停止了:


Thread-1: i= 1
Thread-0: i=1




根据示例代码,来具体讲一下死锁过程:


  • t1线程启动的时候,首先占用了a对象的锁
  • t2线程启动的时候,首先占用了b对象的锁
  • t1线程占用了a对象的锁的同时,尝试拿b对象的锁:b对象的锁,一直在t2线程手里
  • t2线程占用了b对象的锁的同时,尝试拿a对象的锁:a对象的锁,一直在t1线程手里
  • t1和t2线程都处于等待拿对方的锁的状态,
  • 死锁产生