死锁
I.过度使用枷锁可能导致顺序性死锁(Lock-Ordering Deadlock)。
如果所有线程以固定的顺序来获得锁,那么在程序中就不会出现顺序死锁的问题。
示例代码:
public class LeftRightDeadlock {
private final Object left = new Object();
private final Object right = new Object();
public void leftRight(){
synchronized(left){
synchronized(right){
someMethod1();
}
}
}
public void rightLeft(){
synchronized(right){
synchronized(left){
someMethod2();
}
}
}
}
死锁解决方法
1. 可以通过制定锁的顺序,在整个应用程序中都按照这个顺序来获取锁。
示例代码:
public class TransferService {
private static final Object tieLock = new Object();
public void transferMoney(final Account fromAccount, final Account toAccount, double amount)
throws InsufficientFundsException {
int fromHash = System.identityHashCode(fromAccount);
int toHash = System.identityHashCode(toAccount);
/*如果Account中包含唯一的、不可变的,并且具备可比性的键值,
可以使用compareTo方法来确定加锁顺序,从而也不需要“加时赛
(TieBreaking)”锁*/
if (fromHash < toHash) {
synchronized (fromAccount) {
synchronized (toAccount) {
doTransfer(fromAccount, toAccount, amount);
}
}
} else if (fromHash > toHash) {
synchronized (toAccount) {
synchronized (fromAccount) {
doTransfer(fromAccount, toAccount, amount);
}
}
} else {
synchronized (tieLock) {
synchronized (fromAccount) {
synchronized (toAccount) {
doTransfer(fromAccount, toAccount, amount);
}
}
}
}
}
private void doTransfer(Account fromAccount, Account toAccount, double amount)
throws InsufficientFundsException {
if (fromAccount.getBalance().compareTo(new BigDecimal(amount)) < 0) {
throw new InsufficientFundsException();
} else {
fromAccount.debit(amount);
toAccount.credit(amount);
}
}
}
2.使用开放调用。如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用(Open Call)。依赖于开放调用的类通常能表现出更好的行为,并且与那些在调用方法时需要持有锁的类相比,也更易于编写。
示例代码:
class Taxi {
private final Dispatcher dispatcher;
private Point location, destination;
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.notifyAvailable(this);
}
}*/
public void setLocation(Point location) {
boolean reachedDestination;
synchronized (this){
this.location = location;
reachedDestination = location.equals(destination);
}
if (reachedDestination) {
dispatcher.notifyAvailable(this);
}
}
}
class Dispatcher {
private final Set<Taxi> taxis;
private final Set<Taxi> availableTaxis;
public Dispatcher(Set<Taxi> taxis, Set<Taxi> availableTaxis) {
this.taxis = taxis;
this.availableTaxis = availableTaxis;
}
public synchronized void notifyAvailable(Taxi taxi) {
availableTaxis.add(taxi);
}
/*public synchronized Image getImage() {
Image image = new Image();
for (Taxi t : taxis) {
image.drawMarker(t.getLocation());
}
return image;
}*/
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;
}
}
II. 使用线程池和信号量来限制对资源的使用可能导致资源死锁(Resource Deadlock)。
当多个线程相互持有彼此正在等待的锁而又不释放自己已持有的锁时会发生死锁,当它们在相同的资源集合上等待时,也会发生死锁。
死锁的避免与诊断
- 尽量减少潜在的加锁交互数量,将获取锁时需要遵循的协议写入正式文档并始终遵循这些协议;
- 找出在什么地方将获取多个锁(使这个集合尽量小),然后对所有这些实例进行全局分析,从而确保它们在整个程序中获取锁的顺序都保持一致。尽可能地使用开放调用,这能极大地简化分析过程;
- 使用Lock类中的定时tryLock功能来代替内置锁。显式锁可以指定一个超时时限(Timeout),在等待超过该时间后tryLock会返回一个失败信息;
- 通过JVM提供的线程转储(Thread Dump)来帮助识别死锁的发生,UNIX: 向JVM进程发送SIGQUIT信号(kill -3),或按下Ctrl-\键;Windows按下Ctrl-Break键。
参考:《Java并发编程实战》