什么是活跃性障碍
活跃性关注的目标是“某件正确的事情最终会发生”。 当某个操作无法继续执行下去时,就会发生活跃性问题。
活跃性障碍的分类
在所有的活跃性障碍中,最常见的是死锁。除了死锁之外,还有一些其他的活跃性危险,包括饥饿、糟糕的响应性和活锁等。
死锁
如果过多的使用加锁,可能会导致锁顺序死锁;如果使用线程池和信号量来限制对资源的使用,可能会导致资源死锁。
死锁的四个必要条件:
互斥条件:互斥地使用资源。每个资源每次只能给一个进程使用。
请求与保持条件:占有且等待资源。进程在申请新资源得不到满足时,处于等待资源的状态,但不释放已占资源。
不可剥夺条件:不可抢夺资源。任一进程不能抢夺另一进程所占的资源,被占资源只能由占用进程自己来释放。
循环等待条件:循环等待资源。一组进程内部各个进程之间请求资源和占用资源构成了环路。
1.锁顺序死锁
在三种情况下会产生锁顺序死锁:
-
当两个线程试图以不同的顺序来获取相同的两个锁时,就会发生锁顺序死锁。
例如,A线程锁住了left,B线程锁住了right,这时候当A试图获取right,而B视图获取left时,就发生了死锁。
为了避免锁顺序死锁,在设计时要确保线程在获取多个锁时采用一致的顺序。 -
动态的锁顺序死锁。有时候所有的线程似乎都是按照相同的顺序来获得锁,比如银行转账,似乎转账都是从A转到B。
但事实上转账的银行顺序取决于方法的参数顺序,而参数顺序又取决于外部输入。如果一个线程从X向Y转账,另一个从Y向X转账,那么就会发生死锁。 -
在协作对象之间发生的死锁。如果有两个方法的调用会获得两个锁,而有一个锁是重叠的,就有可能发生协作对象之间的死锁。
在这种情况下,可以通过开放调用来避免。所谓开放调用,就是尽量将锁的范围缩小,将同步代码块仅用于保护那些设计共享状态的操作。
2.资源死锁
正如当多个线程相互持有彼此正在等待的锁而又不释放自己已持有的锁时会发生死锁,当它们在相同的资源集合上等待时,也会发生死锁。
3.死锁的避免与诊断
-
支持定时的锁
显式使用Lock类中的定时tryLock功能来代替内置锁机制。
当使用内置锁时,只要没有获得锁,就会永远等待下去,而显式锁则可以指定一个超时机制,在等待超过该事件后tryLock会返回一个失败信息。 -
通过线程转储信息来分析死锁。
JVM可以通过线程转储来帮助识别死锁的发生。类似于发生异常时的栈追踪信息,线程转储会追踪每个线程运行时的栈,以及线程相关的加锁信息。
其他活跃性危险
1.饥饿
当线程由于无法访问它所需要的资源而不能继续执行时,就发生了饥饿。
如果在Java应用程序中对线程的优先级使用不当,或者在持有锁时执行一些无法结束的结构,都可能会导致饥饿。
2.糟糕的响应性
类似于饥饿,由于优先级的问题或者不良的锁管理,某些线程可能会等待很长时间。
3.活锁
活锁是另一种形式的活跃性问题,该问题尽管不会阻塞线程,但也不能继续执行,因为线程将不断重复执行相同的操作,而且总会失败。
活锁通常是由过度的错误恢复代码造成的(如事务的回滚),因为它错误的将不可修复的错误作为可修复的错误。