当调用一个外部的接口时,如果持有锁,由于不知道外部接口做了什么操作,此时,可能由于资源依赖形成锁环路,造成死锁。解决办法是,使用开放调用,调用外部接口时,不持有锁。
先看看错误的调用:如果有两个线程同时调用Taxi的setLocation和Dispatcher的printLocation,则可能因为错误的执行时序造成两个线程都只获得了一个锁,等待另一个永远无法获得的锁,从而造成死锁。
public class Taxi { 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 class Dispatcher { private final Set<Taxi> taxis; private final Set<Taxi> availableTaxis; public Dispatcher() { taxis = new HashSet<>(); availableTaxis = new HashSet<>(); } public synchronized void notifyAvailable(Taxi taxi) { availableTaxis.add(taxi); } public synchronized void printLocation() { for(Taxi taxi :taxis) { System.out.println(taxi.getLocation()); } } }
开放调用:在调用某个方法时不需要持有锁。
修改Taxi类
public void setLocation(Point location) { this.location = location; boolean reachedDestination; synchronized (this) { this.location = location; reachedDestination = location.equals(destination); } if(reachedDestination) { dispatcher.notifyAvailable(this); } }
修改Dispatcher类:
public void printLocation() { Set<Taxi> copy; synchronized (this) { copy = new HashSet<>(taxis); } for(Taxi taxi :taxis) { System.out.println(taxi.getLocation()); } }
但是,使用开放调用技术改造接口会失去原子性,在大多数情况下,非原子性是可以接受的。如果必须保证原子性,则需要设计一些协议,而不是使用加锁,来补偿原子性。例如,使用单线程执行所有任务。