安全性问题是多线程开发的首要问题,为了保证线程安全,往往使用加锁机制。但过度使用锁或不正确使用锁,可能导致死锁等活跃性问题。
线程活跃性问题有:
- 死锁
- 饥饿
- 槽糕的响应性
- 活锁
死锁的原因和解决方案
最常见的活跃性问题是死锁。
死锁最常见的原因是不正确的锁顺序。
- 锁顺序死锁
- 原因:多个锁的循环加锁依赖,如A线程持有一个锁,等待B线程持有的另外一个锁,同时B线程正等待A线程持有的锁。
- 解决方法:按照相同的顺序来请求锁。
- 动态锁顺序死锁
- 原因:锁的顺序取决于传递的参数顺序。
- 解决方法:使用散列值(System.identityHashCode方法)和加时锁方式避免死锁
- 协作对象之间发生死锁
- 原因:在持有锁时调用外部方法,在外部方法中要获得另外的锁。
- 解决方法: 开放调用 (Open Call), 即调用某个方法时不需要持有锁(使用同步块仅被用于保护涉及可变共享状态的操作)。
- 资源死锁
- 原因:1) 在相同的资源集合上等待; 2)线程饥饿死锁,如一个任务依赖其他任务的结果。
死锁的避免和诊断
- 获取多锁时的顺序保持一致。
- 使用开放调用。
- 使用限时的显式锁 (Lock的tryLock方法)。
- 通过线程转储信息来分析死锁。
饥饿
线程由于无法访问它所需的资源而不能继续执行,如线程的优先级使用不当,或程序进入无限循环。
避免使用线程优先级。
槽糕的响应性
将运行时间较长的线程放到后台,并降低它的优先级。
活锁
同常发生在处理事务消息的应用程序中。当多个互相协作的线程对彼此进行响应从而修改各自的状态,并使得任何一个线程无法继续执行时。
解决方法是引入随机性,如让它们分别等待一段随机时间。
示例: 通过开发调用来避免在相互协作的对象之间产生死锁。
class CooperatingNoDeadlock {
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) {
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;
}
}
class Dispatcher {
private final Set<Taxi> taxis;
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) {
}
}
}