当两个或多个线程竞争试图获取对方持有的同步锁时,它们都会处于阻塞状态,除非某个线程主动释放自己所持有的同步锁,这时,死锁就出现了。用下面这张图很好理解:
如图,线程Thread1和Thread2都有两个同步方法Operation1()、Operation2(),Operation1()中会调用Operation2().当某个时候,Thread1获取了A的对像锁,然后失去了CPU时间片,Thread2获得了CPU时间片,然后Thread2获取了B的对象锁,然后它又失去了CPU时间片,CPU时间片重新回到Thread1,这时候Thread1调用Operation2()并试图获取B的对象锁,但此时Thread2持有了B的对象锁,那么Thread1就会阻塞一直等待Thread2的对象锁释放。当CPU切换到Thread2时,它也试图获取A的对象锁,但此时Thread1持有A的对象锁,并且Thread1处于阻塞状态,一直等待着Thread2释放同步锁,因此Thread1跟Thread2就会一直等待对方释放同步锁,这时候,死锁就出现了。
具体示例代码:
class DeadLock{
public int a = 10;
public synchronized void add(DeadLock deadlock){
a++;
deadlock.decrease();
System.out.println("deadthread01---"+a);
}
public synchronized void decrease(){
a--;
System.out.println("deadthread02---"+a);
}
}
class DeadThread01 implements Runnable{
private DeadLock[] deadlock;
public DeadThread01(DeadLock ...deadlock){
this.deadlock = deadlock;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
deadlock[0].add(deadlock[1]);
}
}
}
class DeadThread02 implements Runnable{
private DeadLock[] deadlock;
public DeadThread02(DeadLock ...deadlock){
this.deadlock = deadlock;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
deadlock[1].add(deadlock[0]);
}
}
}
DeadLock deadlock1 = new DeadLock();
DeadLock deadlock2 = new DeadLock();
DeadThread01 thread01 = new DeadThread01(deadlock1,deadlock2);
DeadThread02 thread02 = new DeadThread02(deadlock2,deadlock1);
new Thread(thread01).start();
new Thread(thread02).start();
上面代码中,DeadThread01与DeadThread02都调用了DeadLock的同步方法add()方法,当add()中开始调用同步方法decrease()时,由于两个线程都想试图获取对方的对象锁而发生死锁现象。
死锁的解决方案
死锁的出现不是很频繁,一旦出现也不容易发现,复现率也不高,因此并没有什么很好的简单方法去避免死锁,只能在平时编码的时候多加注意,避免死锁的出现。以下两点是对预防死锁的建议:
- 锁的顺序
- 锁超时
1.锁的顺序:如果保证多个线程获得和释放锁的顺序相同,也就是说如果Thread1和Thread2都是先获取锁A,然后获取锁B,那么死锁就不会发生。
2.锁超时:试图为锁设置超时时间可以避免死锁的发生,也就是说给锁设置一个时间,在这个时间段内,线程如果没有获取锁,那么线程就会自动回退,释放自身持有的锁。这样就给了其他线程获取它的锁的机会,在等待随机的时间后,第一个线程又会试图获取他所需要的锁,这样就可以避免死锁的发生了。但synchronized并没有给同步锁设置时间的功能。这个时候我们就可以用Lock来同步代码了,它可以通过调用tryLock(long,TimeUnit)来设置同步的时间段。比如对上面decrease()方法进行改写成:
ReentrantLock lock = new ReentrantLock();
public void decrease(){
lock.lock();
a--;
System.out.println("deadthread02---"+a);
lock.unlock();
}
这里我们在需要同步的代码临界区开始处调用lock.lock(),在临界区结束处调用lock.unlock(),这样,包含在他们中间的操作就会变为原子操作。这样就不会涉及到死锁的问题。(这段同步代码也可以通过lock.tryLock()方法来设置同步时间)
好了,关于死锁的讨论就到这里了。