A->锁住Left->尝试锁住Right->永久等待
B—>锁住Right->尝试锁住Left->永久等待
//注意:容易发生死锁
public class LeftRightDeadLock{
private final Object left=new Object();
private final Object right=new Object();
public void leftRight(){
synchronized(left){
doSomething();
}
}
public void rightLeft(){
synchronized(right){
doSomething();
}
}
}
在LeftRightDeadLock中发生死锁的原因是:两个线程试图以不同的顺序来获得相同的锁。
*如果所有线程以固定的顺序来获得锁,那么在程序中就不会出现锁顺序死锁问题。*
### 动态的锁顺序死锁
锁的顺序取决于传递的参数顺序
//注意:容易发生死锁!
public void transferMoney(Account fromAccout,
Account toAccout,
DollarAmount amount)
throws InsufficientFundsException{
synchronized(fromAccount){
synchronized(toAccount){
if(fromAccount.getBalance().compareTo(amount)<0) throw="" new="" insufficientfundsexception();="" else{="" fromaccount.debit(amount);="" toaccount.credit(amount);="" }="" <="" pre="">
可以使用System.identityHashCode方法,该方法将返回由Object.hashCode返回的值。散列冲突的频率非常低,为了避免这种情况可以使用”加时赛”锁,在获得两个Account锁之前,首先获得这个”加时赛”锁。或者使用Account中唯一不变,且具备可比性的键值,如账号,代替”加时赛”锁。
private static final Object tieLock=new Object;
public void transferMoney(Account fromAccout,
Account toAccout,
DollarAmount amount)
throws InsufficientFundsException{
class Helper{
public void transfer() throws inesufficientFundsException{
if(fromAcct.getBalance().compareTo(amount)<0) throw="" new="" insufficientfundsexception();="" else{="" fromacct.debit(amount);="" toacct.credit(amount);="" }="" int="" fromhash="System.identityHashCode(fromAcct);" tohash="System.identityHashCode(toAcct);" if(fromhash="" <="" tohash){="" synchronized(fromacct){="" synchronized(toacct){="" helper().transfer();="" }else=""> toHash){
synchronized(toAcct){
synchronized(fromAcct){
new Helper().transfer();
}
}
}else{
synchronized(tieLock){
synchronized(fromAcct){
synchronized(toAcct){
new Helper().transfer();
}
}
}
}
}
0)>
在协作对象之间发生的死锁
如果在持有锁时调用某个外部方法,那么将出现活跃性问题。在这个外部方法中可能会获取其他锁(这可能会产生死锁),或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁。
开放调用
如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用。
public synchronized void setLocation(Point location){
this.location=location;
if(location.equals(destination))
//可能需要获取其他锁
dispatcher.notifyAvailable(this);
}
//修改为开放调用
public synchronized void setLocation(Point location){
boolean reachedDestination;
synchronized(this){
this.location=location;
reachedDestination = location.equals(destination);
}
if(reachedDestination)
dispatcher.notifyAvailable(this);
}
在程序中应尽量使用开放调用。与那些在持有锁时调用外部方法的程序相比,更易于对依赖于开放调用的程序进行死锁分析。
资源死锁
一个任务需要谅解两个数据库,并且在请求这两个资源时不会始终遵循相同的顺序。
线程A持有与数据库D1的连接,并等待与数据库D2的连接,
线程B持有与数据可D2的连接,并等待与数据库D·的连接,将产生资源死锁。
线程饥饿死锁:如果某些任务需要等待其他任务的结果,那么这些任务往往是产生线程饥饿死锁的主要来源,有界线程池/资源池与相互依赖的任务不能一起使用。
显示使用Lock类中的定时tryLock功能来代替内置锁机制。显示锁可以指定一个超时时限,在等待超过该时间后tryLock会返回一个失败信息。
JVM通过线程转存储(Thread Dump)来帮助识别死锁的发生。内置锁与获得它们所在的线程栈帧是相关联的,而显示的Lock只与获得它的线程想关联。
引发饥饿的最常见资源就是CPU时钟周期。如果在Java应用程序中对线程的优先级使用不当,或者在持有锁时执行一些无法结束的结构,那么可能导致饥饿。
要避免使用线程优先级,因为这会增加平台依赖性,并可能导致活跃性问题。在大多数并发应用程序中,都可以使用默认的线程优先级。
如果某个线程长时间占有一个锁(或许正在对一个大容器进行迭代,并且对每个元素进行密集型的处理),而其他想要访问这个容器的线程就必须等待很长时间。
当多个相互协作的线程都对彼此进行相应从而修改各自的状态,并使得任何一个线程都无法继续执行时,就发生了活锁。
要解决这种活锁问题,需要在重试机制中引入随机性。在并发应用程序中,通过等待随机长度的时间和回退可以有效地避免活锁的发生。
0)>