节选自《Java完全参考手册》(第8版)
需要避免的与多任务明确相关的特殊类型的错误是死锁——deadlock,当两个线程循环依赖一对同步对象时,会发生这种情况。例如,假设一个线程进入对象Y的监视器。如果X中的线程试图调用对象Y的任何同步方法,则会被堵塞。但是,如果对象Y中的线程也试图调用A的任何同步方法,那么会永远等待下去,因为为了进入X,必须释放对Y加的锁,这样第一个线程才能完成。死锁是一种很难调试的错误,原因有二:
1.死锁通常很少发生,只有当两个线程恰好以这种方式获取CPU时钟周期时才会发生死锁
2.死锁可能涉及更多的线程以及更多的同步对象(也即是说,死锁可能是通过更加复杂的事件序列发生的
下面的例子创建了两个类——A 和B,他们分别有方法foo(),bar(), 在调用其他类中的方法之前会暂停一会儿。主类Deadlock分别创建一个A和B的实例,然后开始第二个线程以设置死锁条件。方法foo()和barI()使用sleep()作为强制死锁条件发生的手段。
// A example of deadlock
class A {
synchronized void foo( B b ){
String name = Thread.currentThread().getName();
System.out.println(name + " enter A.foo");
try{
Thread.sleep(1000);
} catch ( Exception e ) {
System.out.println("A interrupted");
}
System.out.println( name + " trying to call B.last()");
b.last();
}
synchronized void last() {
System.out.println("Inside A.last");
}
}
class B {
synchronized void bar( A a ) {
String name = Thread.currentThread().getName();
System.out.println(name + " entered B.bar");
try{
Thread.sleep(1000);
} catch ( Exception e ) {
System.out.println(" B interrupted");
}
System.out.println( name + " trying to call A.last()");
a.last();
}
synchronized void last() {
System.out.println("Inside A.last");
}
}
public class DeadLock implements Runnable {
A a = new A();
B b = new B();
DeadLock() {
Thread.currentThread().setName("MainThread");
Thread t = new Thread(this, "RacingThread");
t.start();
a.foo(b); // get lock on a in this thread
System.out.println("Back in main thread");
}
public void run() {
b.bar(a); // get lock on b in other thread
System.out.println("Back in other thread");
}
public static void main ( String [] args) {
new DeadLock();
}
}
当运行这个程序的时候,会看到如下的输出
MainThread enter A.foo
RacingThread entered B.bar
RacingThread trying to call A.last()
MainThread trying to call B.last()
因为程序被死锁,所以你需要按下Ctrl+C 来结束程序。通过在PC上按下Ctrl + Break组合键,可以看到完整的线程和监视器缓存存储(monitor cache dump)。可以看出,在等待a的监视器时,竞态线程拥有b的监视器。同时,主线程拥有a,并且在等待获取b。这个程序永远不会结束。
所以,如果多线程程序偶尔被锁住,那么首先应当检查是否是由于死锁造成的