重温《并发编程实战》---避免活跃性危险

1.一些程序是包含死锁风险的,但是并不会立即显示出来,一般死锁发生在高负载的情况下。

2,死锁类别:

a.)静态锁顺序死锁:如果两个线程试以不同的顺序获得相同的锁,就会产生锁顺序死锁。

如果每个线程都以固定的顺序来获得锁,那么就就不会出现锁顺序死锁。

例子: private final Objectleft = new Object();

private final Objectright = new Object();

public void leftRight(){

synchronized(left){

synchronized(right){

//dosomething();

}

}

}

public void rightLeft(){

synchronized(right){

synchronized(left){

//dosomething();

}

}

}

如果2个线程分别调用leftRight()rightLeft(),则会产生锁顺序死锁,根本原因就不同线程获取锁的顺序的不同。

b.)动态锁顺序死锁,有的时候我们锁的顺序是模糊的,看下列例子。

public void transforMoney(ObjectfromAccount,ObjecttoAccount,int dollar){

synchronized (fromAccount) {

synchronized (toAccount) {

 fromAccount.delete(dollar);

 toAccount.add(dollar);

}

}

}

看以上程序,我们发现无论哪个地方使用了这个方法,锁的顺序”看起来”都是一致的,都是先锁fromAccount,再锁toAccount

此时,就掉入陷阱了,因为你应该知道,方法的参数的顺序取决于方法的调用者,这里的fromAccounttoAccount只是参数定义,实际传入的参数顺序是不一定的,此时仍然会有锁顺序死锁问题。

如果有2个实际参数:youAccountmyAccount

A线程:transforMoney(youAccount,myAccount,20);

B线程:transforMoney(myAccount,youAccount,30);

这时只需要一个不恰当的执行时序,死锁就会发生。

解决办法:我们可以在方法内部通过一些参数的属性来定义锁的顺序。

比如使用System.identityHashCode方法,该方法将返回Object.hashCode()返回的值。例子如下:

private static final ObjectonlyOne= new Object();

public void transforMoney(ObjectfromAccount,Object toAccount,int dollar){

class Transfer{

public void transfer(){

fromAccount.delete(dollar);

toAccount.add(dollar);

}

}

int fromHash = System.identityHashCode(fromAccount);

int toHash = System.identityHashCode(toAccount);

if(fromHash <toHash){

synchronized(fromAccount){

synchronized (toAccount) {

new Transfer().transfer();

}

}

}else if(fromHash >toHash){

synchronized (toAccount) {

synchronized (fromAccount) {

new Transfer().transfer();

}

}

}else{

//唯一锁,同时只有1个线程能获得

synchronized (onlyOne) {

synchronized (toAccount) {

synchronized (fromAccount) {

new Transfer().transfer();

}

}

}

}

                       

一些情况下是会产生哈希冲突,此时必须通过某种任意的方法来决定锁的顺序,而这可能又会重新引入死锁。

解决办法:此时加入一个唯一锁,也称加时锁(private static final 确定的唯一变量,类变量),在获得这两个锁之前,先要获得这个加时锁,从而保证每次只有1个线程以未知的顺序获得这两个锁。

如果时常发生哈西冲突,那么这种加时锁技术会成为并发瓶颈,因为这类似整个程序只有1个锁的情况。

当然我们也可以使用一些唯一的,不可变的属性来定义锁的顺序(例如ID,UUID之类的,可以通过一些属性来对锁进行排序,然后自己定义锁的规则)。

c.)在协作对象之间的锁顺序死锁(通常是因为在持有锁的情况下调用某个外部方法):

协作对象之间的锁获取操作不太明显。看例子:

class A{

private int a,b;

private final BobjectB = new B();

public synchronized void A(int a){

this.a = a;

if(a == b){

objectB.setSomething(b);

}

}

public synchronized void setSomething(int b){

b = a;

}

}

class B{

private int c,d;

private final AobjectA = new A();

public synchronized void setSomething(int d){

d = c;

}

public synchronized void B(int b){

this.c = b;

if(c == d){

objectA.setSomething(c);

}

}

}

                 

A中的A()方法中,我们要先获取this锁,由于调用了B对象的setSomething方法,因为setSomething方法是同步方法,所以必须获得获得setSomething方法需要的锁,也就是B锁。

也就是需要先获得A锁再获得B锁,在B中的B方法也一样,需要先获得B锁再获得A锁。

如果2个线程同时调用A()B(),将发生锁顺序死锁。

这类的锁顺序死锁问题主要因为在持有锁时调用了某个外部方法。         

结论:如果在持有锁的时候调用某个外部方法,那么将出现活跃性问题。在这个外部方法中可能会获取其他锁(获取其他锁导致锁的顺序不一定,可能产生死锁),或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁。   

 

 

d.)开放调用:如果在调用某个方法的时候不需要持有锁,那么这种调用被称为开放调用,在程序中应尽量使用开放调用,与那些在持有锁时调用外部方法的程序相比,更容易对依赖于开放调用的程序进行死锁分析。

   实现方式:不要直接使方法直接成为synchronized的,而是使同步代码块仅仅保护那些涉及共享状态的操作(说白了就是缩小代码块)。

e.)资源死锁:当在相同的资源集合上等待时,也会发生死锁,此时称它为资源死锁。

比如线程A连接了D1资源,需要D2资源。

线程B连接了D2资源,需要D1资源,此时就会产生资源死锁。

线程饥饿死锁也是一种资源死锁,一个任务等待其他任务的结果,那么这些任务往往是产生线程饥饿死锁的来源。

有界线程池/资源池与相互依赖的任务不能一起使用。

3.死锁的解决:a.)不使用内置锁,使用Lock类中的定时tryLock锁来代替内置锁,显示锁可以指定一个超时时限,在等待超时的时候返回一个失败信息。自己再制定恢复或者中断机制。

             b.)通过线程转储来分析死锁:线程转储包含各个运行中县城的栈追踪信息,还包含加锁信息,例如:每个线程持有了那些锁,在哪些栈帧中获得这些锁,以及被阻塞的线程中正在等待获取哪一个锁。

线程转储在内置锁和显示锁上的区别:java6包含对显示锁的线程转储和死锁检测等的支持,但在这些锁上获得的信息比在内置锁上的信息精确度低。内置锁与获得它们所在的线程栈帧是相关联的,而显示的Lock只与它的线程相关联。

4.饥饿:引发饥饿的最常见资源就是”CPU时钟周期

a.)当线程由于无法访问它所需要的资源而不能继续执行时,就发生了”饥饿”问题。        

b.)线程优先级使用不当也会产生饥饿。

c.)在持有锁的时候执行了一些无法结束的结构(例如无限循环或者无限等待某个资源),其他需要这个锁的线程无法得到这个锁,也会产生饥饿问题。

饥饿的关键字是:资源,锁资源,cpu资源等等等。

注意:应该尽量避免使用线程优先级,这会增加平台依赖性,并可能导致活跃性问题。

5.糟糕的响应性:

GUI框架中,如果你的后台任务是cpu密集型的,会与主的事件线程竞争cpu的时钟周期,可能导致cpu主线程的响应性,这时可以降低后台线程的优先级。

不良的锁管理也会导致糟糕响应性。                    

6.活锁:当多个相互协作的线程都对彼此进行响应从而修改各自的状态,并使得任何一个线程都无法继续执行的时候,就发生了活锁。

要解决活锁问题,要引入随机性,不要都采用相同的响应机制(比如都等待相同的时间等)。

猜你喜欢

转载自blog.csdn.net/mypromise_tfs/article/details/72772733