如何避免死锁--JCIPC10读书笔记

本文分析了Java并发环境下转账操作可能导致的死锁问题,并提出了通过一致的锁获取顺序及使用额外锁来解决的方法。

[本文是我对Java Concurrency In Practice C10的归纳和总结.  转载请注明作者和出处,  如有谬误, 欢迎在评论中指正. ]

如果多个线程以不同的顺序持有多个锁, 可能发生死锁:

 

public class AccountTrans {
	public void transferMoney(Account fromAccount, Account toAccount, DollarAmount amount)
			throws InsufficientFundsException {
		synchronized (fromAccount) {
			synchronized (toAccount) {
				if (fromAccount.getBalance().compareTo(amount) < 0)
					throw new InsufficientFundsException();
				else {
					fromAccount.debit(amount);
					toAccount.credit(amount);
				}
			}
		}
	}
}

 

transferMoney方法先后锁定fromAccount和toAccount对象. 如果2个线程以如下的方式调用transferMoney方法:

A: transferMoney(myAccount, yourAccount, 10); 

B: transferMoney(yourAccount, myAccount, 20);

死锁有可能就会发生.

关键在于需要保证以相同的顺序获取多个锁:

 

public class AccountTrans {
	// 额外的锁
	private static final Object tieLock = new Object();

	public void transferMoney(final Account fromAcct, final Account toAcct, final DollarAmount amount)
			throws InsufficientFundsException {
		class Helper {
			public void transfer() throws InsufficientFundsException {
				if (fromAcct.getBalance().compareTo(amount) < 0)
					throw new InsufficientFundsException();
				else {
					fromAcct.debit(amount);
					toAcct.credit(amount);
				}
			}
		}
		// 计算fromAcct和toAcct的hashCode值
		int fromHash = System.identityHashCode(fromAcct);
		int toHash = System.identityHashCode(toAcct);

		// 根据hashCode值确定获取锁的顺序
		if (fromHash < toHash) {
			synchronized (fromAcct) {
				synchronized (toAcct) {
					new Helper().transfer();
				}
			}
		} else if (fromHash > toHash) {
			synchronized (toAcct) {
				synchronized (fromAcct) {
					new Helper().transfer();
				}
			}
		} else {
			// 当hashCode值相同时, 无法确定fromAcct和头Acct锁的获取顺序, 因此增加额外的锁
			synchronized (tieLock) {
				synchronized (fromAcct) {
					synchronized (toAcct) {
						new Helper().transfer();
					}
				}
			}
		}
	}
}
 

open call

所谓open call是指在未持有锁时调用外部方法. 持有锁的时候调用外部方法, 如果被调用的方法需要获取其他的锁, 可能带来死锁的风险. 如果被调用的方法发生阻塞, 当前线程将长时间持有锁, 其他等待获取该锁的线程就会被阻塞.

因此我们应该尽量在未持有锁的时候进行方法的调用.

 

资源死锁

比如线程A持有数据库D1的连接, 并等待获取数据库D2的连接. 而线程B持有数据库D2的连接, 并等待获取数据库D1的连接. 此时就发生了死锁.

资源死锁的另一种形式是线程饥饿死锁, 参见第八章.

 

避免死锁

1. 尽量不要同时持有多个锁.

2. 如果必须同时持有多个锁, 那么保证以一致的顺序获取锁.

3. 尽量在未持有锁的情况下进行方法的调用(open call).

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值