Java并发编程实战笔记(8)-活跃性、性能

Posted by zhidaliao on February 5, 2016

过度使用锁,可能会导致锁锁顺序死锁

T1持有锁A等待锁B , T2持有锁B等待锁A,最简单的死锁形式,称为抱死

每个线程假想为有向图的一个节点,每条边的表示为:一个节点等待下一个节点的锁,如果在图中形成一个环路,那么就存在 死锁

数据库的事务场景:数据库服务器检测到一组事务出现死锁的时候,将选择一个牺牲者并放弃一个事务

JVM发生死锁的时候,只能终止并重启

锁顺序死锁:两个线程按照不同的顺序获取 相同的锁 ; 解决方案: 必须定义锁的顺序,并在整个程序中使用这个顺序来获取锁

比如通过哈希值的大小来决定获取两个锁的顺序

简单的锁顺序死锁

class deadLock{
	Object left = new Object();
	Object right = new Object();

	void leftRight(){
		synchronized(left){
			synchronized(right){
				doSomething();
			}
		}
	}

	void rightLeft(){
		synchronized(right){
			synchronized(left){
				doSomething();
			}
		}
	}

}

动态的锁顺序死锁

转账的场景中,同时发生A转账B , B转账A的时候就会发生死锁

void transfer(Account A,Account B,int money){
	synchronized(A){
		synchronized(B){
			doSomething();
		}
	}
}

改进:极端情况下,哈希值可能一致,也会导致死锁 引入 加时赛锁(tie-break) , 新增一个对象锁在最外层。已未知的顺序获取

class Demo{
    Object tieBreak = new Object();

    void transfer(Account A,Account B,int money){
    	if(A.hash() > B.hasn()){
    		synchronized(A){
    			synchronized(B){
    				doSomething();
    			}
    		}
    	}else if(A.hash() < B.hash()){
    		synchronized(B){
    			synchronized(A){
    				doSomething();
    			}
    		}
    	}else{
    		synchronized(tieBreak){
    			doSomething();
    		}
    	}
    }
}

对于队列而言: 当优先级一样的情况下,就称为 tie-break, 一般是通过谁先出现谁先入栈

协作对象之间发生死锁

场景:

Note: 在持有锁的情况下,调用某个外部方法,那么就需要警惕死锁

class A{
    
    void synchronized a(){
        B.b()
    }
}


class B{

    void synchronized b(){
        A.a()
    }
}

开放调用: 调用某个方法时不需要持有锁 ; 依赖于开放调用的类通常能表现出更好的行为,来避免死锁问题的发生

具体: 将方法的同步块的范围缩小,在方法内部对共享状态进行加锁操作。

死锁的避免与诊断:

  • 如果必须获取多个锁,那么在设计时必须考虑锁的顺序:尽量减少潜在的加锁交互数量,将获取锁时需要遵循的协议写入正式文档中
  • 尽量使用支持定时的锁
  • 通过线程转储信息来分析死锁

其他活跃性风险: