-
死锁包括了几种情况, 例如__锁顺序死锁__、动态的锁顺序死锁、协作对象间的死锁、__资源死锁__等几种情况
-
锁顺序死锁
(1) 两个线程以不同的顺序获得相同的锁, 就会引发锁顺序死锁
示例
public class LeftRightDeadlock { private final Object left = new Object(); private final Object right = new Object(); public void leftRight() { synchronized (left) { synchronized (right) { doSomething(); } } } public void rightLeft() { synchronized (right) { synchronized (left) { doSomethingElse(); } } } void doSomething() { } void doSomethingElse() { } }
这个示例中, 可能会发生两个线程分别执行leftRight()和rightLeft(), 由于它们各持有一把锁, 因此会造成死锁
(2) 如果所有线程以固定的顺序获得锁, 那么在程序中就不会出现锁顺序死锁的问题
-
动态的锁顺序死锁
(1) 看起来正常的代码, 由于参数调用顺序的不同, 可能会动态的引发锁顺序死锁
示例
public class DynamicOrderDeadlock { public static void transferMoney(Account fromAccount, Account toAccount, DollarAmount amount) throws InsufficientFundsException { synchronized (fromAccount) { synchronized (toAccount) { if (fromAccount.getBalance().compareTo(amount) < 0) { throw new InsufficientFundsException("Dead Lock"); } else { fromAccount.debit(amount); toAccount.credit(amount); } } } } ... }
这段代码中的锁是fromAccount和toAccount, 当调用时如果发生两个线程恰好使用了相同的两个账户, 但是顺序相反时, 就有可能造成死锁
(2) 我们无法控制传入参数的顺序, 为了解决这一问题, 需要__定义锁的顺序__
修改后代码
public class InduceLockOrder { 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); } } } int fromHash = System.identityHashCode(fromAcct); int toHash = System.identityHashCode(toAcct); if (fromHash < toHash) { synchronized (fromAcct) { synchronized (toAcct) { new Helper().transfer(); } } } else if (fromHash > toHash) { synchronized (toAcct) { synchronized (fromAcct) { new Helper().transfer(); } } } else { synchronized (tieLock) { synchronized (fromAcct) { synchronized (toAcct) { new Helper().transfer(); } } } } } ... }
修改后的代码通过调用System.identityHashCode()方法获得Object对象的原始hashCode值, 并且始终先获得hashCode小的对象的锁,这样就(貌似)避免了动态锁顺序死锁;
但是有极小的概率出现__两个对象的hash值__相同, 为了避免这个问题引入了专门的锁对象tieLock用来控制只能让一个线程进入;
其实引入tieLock可以解决一切问题, 那么为什么要大费周章的计算hashCode呢? 因为只有tieLock相当于对整个方法加锁, 此时会有性能上的缺陷。
-
在协作对象之间发生的死锁
(1) 这种死锁很难查找, 它发生的情况是获取多个锁的操作发生在不同的方法中(甚至有可能发生在协同的类中), 但是死锁的原因和__锁顺序死锁__一样, 也是获取锁的顺序不同
示例
public class CooperatingDeadlock { // Warning: deadlock-prone! class Taxi { @GuardedBy("this") private Point location, 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.notifyAvailable(this); } } public synchronized Point getDestination() { return destination; } public synchronized void setDestination(Point destination) { this.destination = destination; } } class Dispatcher { @GuardedBy("this") private final Set<Taxi> taxis; @GuardedBy("this") private final Set<Taxi> availableTaxis; public Dispatcher() { taxis = new HashSet<Taxi>(); availableTaxis = new HashSet<Taxi>(); } 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; } } class Image { public void drawMarker(Point p) { } } }
这个示例中, Taxi和Dispatcher是两个耦合的类, 一个代表出租车类(它和一个调度对象关联), 一个代表调度(它和一组出租车关联)。 发生死锁的情形可能是: 一个出租车对象想要setLocation, 那么它先持有自身的锁, 希望获得关联的Dispatcher的锁; 一个Dispatcher想要getImage, 那么它先持有自身的锁, 希望获得各个taxi的锁。 此时就出现了死锁, 只不过由于死锁发生在不同类的不同方法之间很难查找
(3) 如果在持有锁的情况下调用某个外部方法, 那么就需要死锁。当在这个外部方法可能会获取其他锁时发生死锁,或者阻塞时间过长, 就会导致其他线程无法及时获得当前持有的锁, 引发活跃性问题
(4) 解决的方式是__尽可能使用开放调用__
1° 开放调用: 如果在调用某个方法时不需要持有锁, 那么这种调用称为开放调用
2° 封装方便了线程安全性的分析, 那么开放调用就方便了程序活跃性的分析
3° 同步代码块应该仅被用于保护那些设计共享状态的操作, 收缩同步代码块的范围可以提高伸缩性
4° 修改后的Taxi和Dispatcher示例(不再出现死锁)
class CooperatingNoDeadlock { @ThreadSafe class Taxi { @GuardedBy("this") private Point location, 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.notifyAvailable(this); } } public synchronized Point getDestination() { return destination; } public synchronized void setDestination(Point destination) { this.destination = destination; } } @ThreadSafe class Dispatcher { @GuardedBy("this") private final Set<Taxi> taxis; @GuardedBy("this") private final Set<Taxi> availableTaxis; public Dispatcher() { taxis = new HashSet<Taxi>(); availableTaxis = new HashSet<Taxi>(); } public synchronized void notifyAvailable(Taxi taxi) { availableTaxis.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; } } class Image { public void drawMarker(Point p) { } } }
修改后的示例没有将setLocation和getImage方法在整个方法体上同步, 而是采取了局部代码块同步, 这样不存在一个方法在持有锁的同时还请求锁的情况, 也就避免了死锁
-
资源死锁
(1) 之前的几种死锁情况是不同线程都在等待彼此拥有的锁, 资源死锁指的是不同线程在等待其他线程拥有的资源。
例如数据库连接池中只有一个连接, 现在两个线程都需要与两个数据库连接, 一个线程占据着A数据库请求B数据库, 另一个线程占据着B数据库请求A数据库, 此时就会发生死锁。
chapter10_避免活跃性危险_1_死锁
最新推荐文章于 2021-10-11 17:49:16 发布