java并发编程(九): 避免活跃性危险

避免活跃性危险:

  • 本部分讨论活跃性故障的缘由,及如何避免它们。

死锁:

  • 典型的哲学家进餐问题。

锁顺序死锁:

如上面哲学家进餐有可能发生下面的状况: java

  • 上面发生死锁的根本缘由在于两个线程以不一样的顺序来获取相同的锁
  • 若是全部线程都以固定的顺序获取锁,那么程序就不会出现锁顺序死锁问题。
/**
 * 容易由于获取锁的顺序致使死锁
 */
public class LeftRightDeadLock {
	private final Object left = new Object();
	private final Object right = new Object();
	
	public void leftRight(){
		synchronized(left){
			synchronized(right){
				// to do sth.
			}
		}
	}
	
	public void rightLeft(){
		synchronized(right){
			synchronized(left){
				// to do sth.
			}
		}
	}
}

动态的锁顺序死锁:

  • 典型的就是银行转帐问题
public void transferMoney(Account fromAccount, Account toAccount, int money){
	synchronized (fromAccount) {
		synchronized(toAccount){
			if (fromAccount.getBalance() > money){
				//余额不足
			} else{
				fromAccount.debit(money);
				toAccount.credit(money);
			}
		}
	}
}
当咱们如下面这种方式调用,就有可能出现死锁:
transferMoney(a1, a2, money);
transferMoney(a2, a1, money);
要解决这种问题,就得使内部以相同的顺序加锁,不管外部怎么调用。
/**
 * 用于当输入参数的hash值同样时使用
 */
private static final Object tieLock = new Object();
	
public static void transferMoney(final Account fromAccount, 
		final Account toAccount, final int money){
	class Helper{
		public void transfer(){
			if (fromAccount.getBalance() < money){
				//余额不足
			} else{
				fromAccount.debit(money);
				toAccount.credit(money);
			}
		}
	}
		
	int fromHash = System.identityHashCode(fromAccount);
	int toHash = System.identityHashCode(toAccount);
		
	//不管客户端怎么传入参数,咱们都以先锁定hash值小的,再锁定hash大的
	//也能够利用业务中排序关系,如Account的编号等来比较
	if (fromHash < toHash){
		synchronized (fromAccount){
			synchronized (toAccount) {
				new Helper().transfer();
			}
		}
	} else if (fromHash >  toHash){
		synchronized (toAccount){
			synchronized (fromAccount) {
				new Helper().transfer();
			}
		}
	} else { //hash值相等, 状况很小
		synchronized (tieLock) {
			synchronized (fromAccount) {
				synchronized (toAccount) {
					new Helper().transfer();
				}
			}
		}
	}
}
在协做对象之间发生的死锁:
  • 即在不一样方法中相互持有等待得锁。
class Taxi {
	private Point location; 
	private Point destination;
	private final Dispatcher dispatcher;

	public Taxi(Dispatcher dispatcher) {
		this.dispatcher = dispatcher;
	}
		
	public synchronized Point getLocation(){
		return location;
	}
		
	public synchronized void setLocation(Point location){
		this.location = location;
		if (location.equals(destination)){
			dispatcher.notifyAvaliable(this);
		}
	}
}
	
class Dispatcher {
	private final Set<Taxi> taxis;
	private final Set<Taxi> avaliableTaxis;
		
	public Dispatcher(){
		taxis = new HashSet<>();
		avaliableTaxis = new HashSet<>();
	}

	public synchronized void notifyAvaliable(Taxi taxi) {
		avaliableTaxis.add(taxi);
	}
		
	public synchronized Image getImage(){
		Image image = new Image();
		for (Taxi t :taxis){
			image.drawMarker(t.getLocation());
		}
		return image;
	}
}
上面的 setLocationgetImage就有可能发生死锁现象:setLocation获取到Taxi对象锁后,在dispacher.notifiyAvaliable()时须要dispatcher锁,而getImage获取到dispacher锁后,t.getLocation要求Taxi锁。
  • 若是在持有锁时调用某个外部方法,那么将出现活跃性问题,在这个外部方法中可能会获取其余锁(这可能会产生死锁),或者阻塞时间过长,致使其余线程没法及时得到当前被持有的锁。

开放调用:

  • 若是在调用某个方法时不须要持有锁,那么这种调用就被称为开放调用(Open Call)
/**
 * 经过公开调用来避免在相互协做的对象之间产生死锁
 */
public class OpenCall {
	class Taxi {
		private Point location; 
		private Point destination;
		private final Dispatcher dispatcher;

		public Taxi(Dispatcher dispatcher) {
			this.dispatcher = dispatcher;
		}
		
		public synchronized Point getLocation(){
			return location;
		}
		
		public void setLocation(Point location){
			boolean reachedDestination;
			synchronized(this){
				this.location = location;
				reachedDestination = location.equals(destination);
			}
			if (reachedDestination){
				dispatcher.notifyAvaliable(this); //这里持有dispatcher锁,但已释放taxi锁
			}
		}
	}
	
	class Dispatcher {
		private final Set<Taxi> taxis;
		private final Set<Taxi> avaliableTaxis;
		
		public Dispatcher(){
			taxis = new HashSet<>();
			avaliableTaxis = new HashSet<>();
		}

		public synchronized void notifyAvaliable(Taxi taxi) {
			avaliableTaxis.add(taxi);
		}
		
		public Image getImage(){
			Set<Taxi> copy;
			synchronized (this) {
				copy = new HashSet<Taxi>(taxis);
			}
			Image image = new Image();
			for (Taxi t :copy){
				image.drawMarker(t.getLocation());//调用外部方法前已释放锁
			}
			return image;
		}
	}
}
  • 在程序中尽可能使用开放调用。与那些在持有锁调用外部方法的程序时,更易于对依赖于开放调用的程序进行死锁分析。

资源死锁:

  • 多个资源池(如数据库链接池),一个线程须要链接2个数据库链接,如线程A持有Pool1的链接,等待Pool2的链接;线程B持有Pool2的链接,等待Pool1的链接。
  • 线程饥饿死锁,如一个任务中提交另外一个任务,并一直等待被提交任务完成。
  • 有界线程池/资源池与相互依赖的任务不能一块儿使用。

死锁的避免与诊断:

支持定时的锁:

  • 限时等待。如Lock中的tryLock, 给定一个超时时限,若等待超过该时间,则会给出错误信息,避免永久等待。

经过线程转储信息来分析死锁:

其余活跃性危险:

饥饿:

  • 因为线程没法访问它所需的资源而不能继续执行时,就发生了"饥饿"。
  • 避免使用线程优先级,这会增长平台依赖性,并可能致使活跃性问题,在大多数并发应用程序中,均可以使用默认的线程优先级。

糟糕的响应性:

  • 例如GUI程序中使用了后台线程。该后台任务若为CPU密集型,将可能影响程序响应性。
  • 不良的锁管理也可能致使糟糕的响应性。

活锁:

  • 活锁是另外一种形式的活跃性问题,该问题尽管不会阻塞线程,但也不能继续执行,由于线程将不断重复执行相同的操做,并且总会失败
  • 多个相互协做的线程都对彼此进行响应从而修改各自的状态,并使得任何一个线程都没法继续执行时,就发生了活锁。
  • 在并发应用中,经过等待随机长度的时间和回退能够有效的避免活锁的发生。

不吝指正。 数据库

相关文章
相关标签/搜索