本文共 3509 字,大约阅读时间需要 11 分钟。
本节书摘来异步社区《Java线程与并发编程实践》一书中的第2章,第2.3节,作者: 【美】Jeff Friesen,更多章节内容可以访问云栖社区“异步社区”公众号查看。
活跃性这个词代表着某件正确的事情最终会发生。活跃性失败发生在应用程序触及一种无法继续执行的状态。在单线程的应用程序中,无限循环就是一个例子。多线程应用程序面临着诸如死锁、活锁和饿死的额外挑战。
死锁:线程1等待线程2互斥持有的资源,而线程2也在等待线程1互斥持有的资源。两条线程都无法继续执行。
活锁:线程x持续重试一个总是失败的操作,以致于无法继续执行。饿死:线程x一直被(调度器)延迟访问其赖以执行的资源。或许是调度器先于低优先级的线程执行高优先级的线程,而总是有一个高优先级的线程可以执行。饿死通常也称为无限延迟。死锁会发生在synchronized关键字带来的过多同步上。如果不小心,你可能就会遭遇锁同时被多条线程竞争的情形;即线程自身缺失继续执行的锁,却持有其他线程需要的锁,同时由于其他线程持有临界区的锁,导致没有一条线程能够通过临界区,进而释放自己所持有的锁。清单2-1就是描述该场景的一个典型例子。清单2-1 一个死锁的问题
public class DeadlockDemo{ private final Object lock1 = new Object(); private final Object lock2 = new Object(); public void instanceMethod1() { synchronized(lock1) { synchronized(lock2) { System.out.println("first thread in instanceMethod1"); // critical section guarded first by // lock1 and then by lock2 } } } public void instanceMethod2() { synchronized(lock2) { synchronized(lock1) { System.out.println("second thread in instanceMethod2"); // critical section guarded first by // lock2 and then by lock1 } } } public static void main(String[] args) { final DeadlockDemo dld = new DeadlockDemo(); Runnable r1 = new Runnable() { @Override public void run() { while(true) { dld.instanceMethod1(); try { Thread.sleep(50); } catch (InterruptedException ie) { } } } }; Thread thdA = new Thread(r1); Runnable r2 = new Runnable() { @Override public void run() { while(true) { dld.instanceMethod2(); try { Thread.sleep(50); } catch (InterruptedException ie) { } } } }; Thread thdB = new Thread(r2); thdA.start(); thdB.start(); }}```清单2-1中线程A和线程B在不同的时间分别调用了instanceMethod1()和instanceMethod2()方法。参考下面的执行序列:(1)线程A调用instanceMethod1(),获取到lock1引用对象的锁,然后进入它外部的临界区(但是还没有获取lock2引用对象的锁)。(2)线程B调用instanceMethod2(),获取到lock2引用对象的锁,然后进入它外部的临界区(但是还没有获取lock1引用对象的锁)。(3)线程A尝试去获取和lock2相关联的锁。JVM强制线程在内部临界区之外等待,由于线程B持有那个锁。(4)线程B尝试去获取和lock1相关联的锁。JVM强制线程在内部临界区之外等待,由于线程A持有那个锁。(5)由于其他线程持有了必要的锁,没有一条线程能继续执行。遭遇死锁,程序(至少在这两条线程的上下文中)就冻结住了。照下面那样编译清单2-1:
javac DeadlockDemo.java
运行程序:java DeadlockDemo`
尽管前面的例子很清晰地识别了死锁的状态,但侦测死锁还是不容易。举个例子,你的代码可能在多个类中(在多个源文件里)包含如下的环形关系:
类A的同步方法调用了类B的同步方法。
类B的同步方法调用了类C的同步方法。类C的同步方法调用了类A的同步方法。如果线程A调用了类A的同步方法,线程B调用了类C的同步方法,因为线程A还在那个方法当中,当线程B尝试调用类A的同步方法时,会被阻塞住。线程A会继续执行直至其调用类C的同步方法,然后阻塞住。死锁发生了。注意:
>Java语言和JVM都没有提供一种方式来避免死锁,所以这一任务就落到你的身上。避免死锁最简单的方式,就是阻止同步方法或者同步块调用其他的同步方法和同步块。尽管这个建议能避免死锁发生,但还是不现实,因为,在你的某一个同步方法和同步块中,很可能需要调用Java API中的同步方法,而且这个建议有点因噎废食,因为被调用的同步方法和同步块很可能不会调用其他的同步方法和同步块,从而不会导致死锁发生。转载地址:http://dvpxx.baihongyu.com/