一个并发应用程序能及时执行的能力称为活跃性。本节将介绍最常见的活跃性问题:死锁(deadlock),以及另外两个活跃性问题:饥饿(starvation)和活锁(livelock)。
死锁
死锁描述了这样一种情景,两个或多个线程永久阻塞,互相等待对方释放资源。下面是一个例子。
Alphone和Gaston是朋友,都很讲究礼节。礼节有一个严格的规矩,当你向一个朋友鞠躬时,你必须保持鞠躬的姿势,直到你的朋友有机会回鞠给你。不幸的是,这个规矩没有算上两个朋友相互同时鞠躬的可能。
下面的应用例子,DeadLock,模拟了这个可能性。
static class Friend { private final String name; public Friend(String name) { this.name = name; } public String getName() { return this.name; } public synchronized void bow(Friend bower) { System.out.format("%s: %s" + " has bowed to me!%n", this.name, bower.getName()); bower.bowBack(this); } public synchronized void bowBack(Friend bower) { System.out.format("%s: %s" + " has bowed back to me!%n", this.name, bower.getName()); } } public static void main(String[] args) { final Friend alphonse = new Friend("Alphonse"); final Friend gaston = new Friend("Gaston"); new Thread(new Runnable() { public void run() { alphonse.bow(gaston); } }).start(); new Thread(new Runnable() { public void run() { gaston.bow(alphonse); } }).start(); } }
当DeadLock运行后,两个线程极有可能阻塞,当它们尝试调用bowBack方法时。没有哪个阻塞会结束,因为每个线程都在等待另一个线程退出bow方法。
饥饿和活锁
饥饿和活锁并不如死锁一般普遍,但它仍然是每个并发程序设计者可能会遇到的问题。
饥饿
饥饿是指当一个线程不能正常的访问共享资源并且不能正常执行的情况。这通常在共享资源被其他“贪心”的线程长期时发生。举个例子,假设一个对象提供了一个同步方法,这个方法通常需要执行很长一段时间才返回。如果一个线程经常调用这个方法,那么其他需要同步的访问这个对象的线程就经常会被阻塞。
活锁
一个线程通常会有会响应其他线程的活动。如果其他线程也会响应另一个线程的活动,那么就有可能发生活锁。同死锁一样,发生活锁的线程无法继续执行。然而线程并没有阻塞——他们在忙于响应对方无法恢复工作。这就相当于两个在走廊相遇的人:Alphonse向他自己的左边靠想让Gaston过去,而Gaston向他的右边靠想让Alphonse过去。可见他们阻塞了对方。Alphonse向他的右边靠,而Gaston向他的左边靠,他们还是阻塞了对方。