java多线程学习笔记之8 什么是死锁,如何避免死锁发生

Simplistically, deadlock occurs when two or more threads are waiting for two or more locks to be freed and the circumstances in the program are such that the locks are never freed. Interestingly, it is possible to deadlock even if no synchronization locks are involved. A deadlock situation involves threads waiting for conditions; this includes waiting to acquire a lock and also waiting for variables to be in a particular state. On the other hand, it is not possible to deadlock if only one thread is involved, as Java allows nested lock acquisition. If a single user thread deadlocks, a system thread must also be involved.

简单地说,当两个或多个线程等待释放两个或多个锁,并且程序中的情况是锁永远不会被释放时,就会发生死锁。有趣的是,即使不涉及同步锁,也可能出现死锁。死锁情况涉及线程等待条件;这包括等待获取锁,以及等待变量处于特定状态。另一方面,如果只涉及一个线程,就不可能死锁,因为Java允许嵌套锁获取。如果单用户线程死锁,则还必须涉及系统线程。

一般来说,死锁是很难解决的.一个死锁有可能会让开发者无法不通过全部重新设计已经编写好的程序来解决.就因为复杂到了这种程度,所以死锁通常是不可能轻松解决的,或是某种程度上仅能够期待由底层的系统自动地解决掉死锁问题.对于开发者来说唯一可以做的是预防写出能够造成死锁的程序,才是首要任务,也是可以把握的事情.

Can the system somehow resolve this deadlock, just as it is able to avoid the potential deadlock when a thread tries to grab the same lock again? Unfortunately, this problem is different. Unlike the case of the nested locks, where a single thread is trying to grab a single lock multiple times, this case involves two separate threads trying to grab two different locks. Since a thread owns one of the locks involved, it may have already made changes that make it impossible for it to free the lock. To be able to fix this problem at the system level, Java would need a system where the first lock can't be grabbed until it is safe from deadlock or provide a way for the deadlock to be resolved once it occurs. Either case is very complex and may be more complex than just having the developer design the program correctly 

系统能否以某种方式解决这个死锁,就像它能够避免线程再次尝试获取同一个锁时可能出现的死锁一样?不幸的是,这个问题是不同的。与嵌套锁的情况不同,在嵌套锁中,一个线程试图多次获取一个锁,而这种情况涉及两个单独的线程试图获取两个不同的锁。由于一个线程拥有其中一个锁,它可能已经进行了更改,无法释放锁。为了能够在系统级解决这个问题,Java需要一个系统,在这个系统中,第一个锁在安全解除死锁之前不能被抓取,或者在死锁发生后提供一种解决死锁的方法。这两种情况都非常复杂,可能比让开发人员正确设计程序更复杂 

In general, deadlocks can be very difficult to resolve. It is possible to have a deadlock that developers can't fix without a complete design overhaul. Given this complexity, it is not possible, or fair, to expect the underlying system to resolve deadlocks automatically. As for the developer, we look at the design issues related to deadlock prevention and even develop a tool that can be used to detect a deadlock 

一般来说,僵局(死锁)可能很难解决。如果没有彻底的设计改革,开发人员可能无法修复死锁。鉴于这种复杂性,期望底层系统自动解决死锁是不可能的,也不公平的。至于开发人员,我们研究了与死锁预防相关的设计问题,甚至开发了一个可用于检测死锁的工具 

 简单的说:死锁就是会发生在两个或两个以上的thread在等待两个或两个以上的lock会被释放,且程序的环境却让lock永远无法释放(获释),这种情况下就会发生死锁,有意思的是即使没有涉及同步lock也有可能产生死锁现象,这种情况下死锁的状况涉及thread等待一些条件,这包括了等着要取得lock且同时也等着某变量进入特定的状态。

———————————————————————————————— 

死锁与自动释放的Lock(锁):
还有一些在使用Lock interface(明确锁)实现的方案(或者任何不是使用synchronized关键字的锁机制)时关于死锁问题的考虑。启发的案例如下:
public void resetScore(){
scoreLock.lock();
charLock.lock();
score=0;
char2Type=-1;
setScore();
scoreLock.unlock();
charLock.unlock();
}

然而,如果调用resetScore();方法的thread遭遇到运行时的异常而终结往下运行该法的时候会发生什么事情呢?在许多的threading系统中,这种情况会产生死锁状况,因为该发生了异常的resetScore()方法的中断执行并不会自动地释放掉它所持有的同步锁(lock).在那样的系统中,其他的想要获取该同步锁lock的thread在尝试获取该同步锁的时候会永远也获取不了,比如要调用相同的resetScore()方法的时候由于永远也得不到该锁时会无止境地等待下去,从而引发死锁状态.然而,在Java中跟在synchronized关键字所绑定的同步锁都会在获取该锁的thread离开该用synchronized关键字声明的同步代码段的范围后被自动释放掉(这是个固有特性),即使是因为异常的发生而离开该synchronized声明的同步范围也是一样有该特性的,所以得出结论:在Java中使用synchronized关键字所声明的同步代码段不会引发死锁的发生!但是我们用Lock interface方式来代替synchronized关键字的使用时Java是不可能会知道此明确锁(lock)的范围,开发者的想法可能是甚至在异常发生的状态时都还想要持有同步锁lock。因此如上例中如果setScore()方法抛出了运行时异常,因为其后面的scoreLock.unlock();与charLock.unlock()方法并没有被调用,所以该同步锁lock永远不会被释放掉!!有一个简单的方法可以解决该问题:我们可以用Java的finally子句来确保同步锁会在完成时释放掉,而不管method是怎么离开的,即利用finally子句中书写释放锁的unlock();方法即使在获取锁后发生了方法体内的异常,最终也会在finally子句的作用下完成对锁lock的unlock();操作以完成对锁的释放!。顺便说一下,这个synchronized关键字的反死锁行为(无条件离开时会释放锁)不一定是一件好事,例如当thread在持有同步锁后而遇到运行时异常时,这会使得数据保持在不一致的状态中。如果后续有其他的thread能够取得该lock,这种情况下可能会产生不一致的数据并且不正确地处理下去。
——————————————————————————————————————————————————————————————————————————————
注意:当使用明确的lock机制时(Lock interface实现的明确锁机制)你应该不只是使用finally子句来释放掉lock,还应该要测试运行时异常的状况,之后还得清除该异常。很不幸地,按照Java的语义,这个问题不可能完全地在使用synchronized关键字或使用finally子句时被解决掉。事实上,它正是引发stop()这个method被废除的原因:stop()方法是通过抛出异常来运行的,这有可能会让Java virtual machine 中的关键资源不能维持在一致的状态.因为我们不能完全地解决这个问题,也许有时候最好就使用明确的lock并在thread意料之外离开的时候冒死锁的风险.因为有死锁的系统也比会出现数据处理错误的系统好多了!

public class DeathLockTest{

private Lock adminLock = new ReentrantLock();
private Lock charLock = new ReentrantLock();
private Lock scoreLock = new ReentrantLock();

public void resetScore(){

try{
charLock.lock();
scoreLock.lock();
.....
.....//需要同步的代码段1

}
finally{
charLock.unlock();
scoreLock.unlock();
}
}

public void newCharacter(CharacterEvent ce){

try{
scoreLock.lock();
charLock.lock();
.....
.....//另一段需要同步的代码段2


}
finally{
scoreLock.unlock();
charLock.unlock();


}

}


}
_死锁的问题在于它导致程序无止境地等待!___________________________________________________________________
如同我们之前提过的,这个范例是简单的,但依此相同的原理还会有更加复杂的死锁构成状况,它们是很难检测到的,但无一例外是涉及两个或者两个以上的thread尝试要取得其他thread所持有的lock的情况,更确切地说是等待所造成冲突的状况)
__________________________________________________________
注意:仔细检查程序代码是目前唯一可用来检测是否可能发生死锁的途径,别无他法!java virtual machine 在运行时期间并没有检测死锁的机制可用!避免死锁的最简单的方法就是编程时要讲究,要遵循规范规则!
——————————————————————————————————————————————————————————————————————————————
(重要知识点)
避免死锁最实际的规则就是确保lock都以相同的顺序被取得!即被同步的代码在获取各个lock的时候应当保持获取各个lock的顺序保持一致,按照以上的死锁案例来说,不管是scoreLock还是charLock都必须用一致的获取顺序(哪一个先取得并不重要,重要的是顺序一致就好,换句话说获取lock要有个一致的层次结构!)

上例中避免死锁发生改正后的代码如下:

public class DeathLockTest{

private Lock adminLock = new ReentrantLock();
private Lock charLock = new ReentrantLock();
private Lock scoreLock = new ReentrantLock();

public void resetScore(){

try{
charLock.lock();
scoreLock.lock();
.....
.....//需要同步的代码段1

}
finally{
charLock.unlock();
scoreLock.unlock();
}
}

public void newCharacter(CharacterEvent ce){

try{
charLock.lock();
scoreLock.lock();
.....
.....//另一段需要同步的代码段2


}
finally{
charLock.unlock();
scoreLock.unlock();


}

}


}

(注意)使用Lock interface 的同步锁机制(明确的同步锁使用)比使用synchronized关键字实现的同步代码发生死锁的概率要小的多!因此涉及到同步锁机制的代码最好采用明确的同步锁机制实现的方式来书写!(因为这样思路清晰不容易写出死锁效应的代码)即提倡运用实现了Lock接口的明确范围的同步锁机制来代替synchronized关键字的使用!
______________________________________________________________________________________________________________________________________________________


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值