在安全性和活跃性之间通常存在某种制衡。我们使用加锁机制来确保安全性,但是不合理的使用加锁,可能导致锁顺序死锁;同样我们使用线程池和信号量来限制对资源的使用,但是这些限制行为可能会导致资源死锁。
java应用程序无法从死锁中恢复过来(会造成性能下降甚至没有响应,不像数据库有解决死锁的方案,只能重启服务),因此设计时一定要排除那些可能导致死锁出现的条件;
本文介绍一些导致活跃性故障的原因以及如何避免
1:死锁
所谓死锁是指多个进 程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进;
可以看看这里,有死锁相关的介绍:http://see.xidian.edu.cn/cpp/html/2604.html
1.1锁顺序死锁
两个线程试图以不同的顺序来获得相同的锁,例如,锁L和M,线程A按照L/M的顺序请求锁,线程B按照M/L的顺序请求锁,可能会出现锁顺序死锁;
如果按照相同的顺序请求锁,就不会出现加锁的依赖性,也就不会产生锁顺序死锁;
public class LeftRightLock{
private final Object left=new Object();
private final Object right=new Object();
public void leftRight(){
synchronized (left){
synchronized(right){
<span style="white-space:pre"> </span> doSomething();
}
}
}
public void rightLeft(){
synchronized (right){
synchronized(left){
doSomething();
}
}
}
}
1.2动态的锁顺序死锁
public void transferMoney(Account fromAccount,Account toAccount,int money){
synchronized(fromAccount){
synchronized(toAccount){
doSomeThing();
}
}
}
可能发生死锁,看起来是按照固定顺序加锁,但是实际的加锁顺序与传入的参数有关。
如果两个线程同时执行:
transferMoney(fromAccount,toAccount,10)
transferMoney(toAccount,fromAccount,20)有可能发生死锁
如何解决?要在整个程序中按照固定的顺序加锁
public static final Object tielock=new Object()
public void transferMoney(Account fromAccount,Account toAccount,int money){
int fromHash=fromAccount.identityHashCode(fromAccount)
int toHash=toAccount.identityHashCode(toAccount)
if(fromAccount<toAccount){
synchronized(fromAccount){
synchronized(toAccount){
doSomeThing();
}
}
}else if(fromAccount>toAccount){
synchronized(toAccount){
synchronized(fromAccount){
doSomeThing();
}
}
}else{
synchronized(tielock){
synchronized(toAccount){
synchronized(fromAccount){
doSomeThing();
}
}
}
}
1.3在协作对象之间发生死锁
如果在持有锁时调用某个外部方法,那么将有可能出现活跃性问题。在这个外部调用中可能会获取其他锁(可能产生死锁),或者阻塞时间过长,导致其他线程无法获得当前持有的锁;
class A{
private B b;
public synchronized void f(){
doSomethingA();
doSomethingB();
b.method();
}
}
1.4开放调用
如果在调用某个方法时不需要获取锁,那么这种调用被称为开放调用;通过开放调用可以解决上述的在协作对象间发生死锁的现象;
收缩加锁代码块,可能会损失操作的原子性
class A{
private B b;
public void f(){
synchronized{
doSomethingA();
doSomethingB();
}
b.method();
}
}
2:死锁的避免与诊断
2.1支持定时的锁
2.2通过线程转储信息来分析死锁
3:其他活跃性危险
3.1饥饿
当线程无法访问它所需的资源而不能继续执行时,就发生了饥饿现象,引发饥饿现象的最常见资源就是CPU。如果在java程序对对线程的优先级使用不当或者持有锁的时候执行一些无法结束的结构(例如无限循环、无限制的等待某个资源)也可能导致线程饥饿;
应该避免使用线程优先级,这会增加平台依赖,并导致活跃性问题,最好使用默认线程优先级