避免活跃性危险(第十章)

避免活跃性危险

在安全性与活跃性之间一般存在着某种制衡,咱们使用加锁机制来确保线程安全,但若是过分地使用加锁,则可能致使“锁顺序死锁”。一样,咱们使用线程池和信号量来限制对资源的使用,但这些被限制的行为可能会致使资源死锁。安全

1. 死锁

  • 锁顺序死锁:两个线程试图以不一样的顺序来得到相同的锁,若是按照相同的顺序来请求锁,那么就不会出现循环的加锁依赖性,所以也就不会产生死锁。 在制定锁的顺序时,可使用System.identityHashCode方法,该方法返回有Object.hashCode返回的值,经过比较大小等方法定义锁的顺序。在某些状况下,两个对象可能拥有相同的散列值,此时必须经过某种方法来决定锁的顺序,而这可能会从新引入死锁,为了不这种状况,可使用“加时赛”锁,在得到两个对象的锁以前,首先得到这个加时赛锁,从而保证每次只有一个线程以未知的顺序得到这两个锁。并发

  • 在协做对象之间发生的死锁 若是在持有锁的状况下调用某个外部方法,那么就须要警戒在协做对象之间发生死锁。 若是在持有锁时调用某个外部方法,那么将出现活跃性问题,在这个外部方法中可能会得到其余锁(这可能会产生死锁),或者阻塞时间过长,致使其余线程没法及时得到当前被持有的锁。ide

  • 开放调用 若是在调用某个方法时不须要持有锁,那么这种调用被称为开放调用。 在程序中应尽可能使用开放调用。与那些在持有锁时调用外部方法的程序相比,更易于对依赖于开放调用的程序进行死锁分析。线程

  • 资源死锁设计

  1. 线程饥饿死锁。若是某些任务须要等待其余任务的结果,那么这些任务每每是产生线程饥饿死锁的主要来源。
  2. 有界线程池/资源池与相互依赖的任务不能一块儿使用

2. 死锁的避免与诊断

  1. 若是一个程序每次至多只能得到一个锁,那么就不会产生锁顺序死锁。
  2. 若是必须获取多个锁,那么在设计时必须考虑锁的顺序:尽可能减小潜在的加锁交互数量,将获取锁时须要遵循的协议写入正式文档并始终遵循这些协议。
  3. 在使用细粒度锁的程序中,能够经过使用一种两阶段策略来检查代码中的死锁:首先,找出在什么地方将获取多个锁,而后对全部这些实例进行全局分析,从而确保它们在整个程序中获取锁的顺序都保持一致。
  4. 使用显示锁Lock类中的定时tryLock功能,在等待超过指定时间后tryLock会放回一个失败信息。

3. 其余活跃性危险

  1. 饥饿:当线程因为没法访问它所须要的资源而不能继续执行时,就发生了“饥饿”。

引起饥饿的最多见资源就是CPU时钟周期,若是在Java应用程序中对线程的优先级使用不当,或者在持有锁时执行一些没法结束的结构(例如无限循环,或者无限制地等待某个资源),那么也可能致使饥饿,由于其余须要这个锁的线程将没法获得它。线程优先级并非一种直观的机制,而经过修改线程优先级所带来的效果一般也不明显。当提升某个线程的优先级时,可能不会起到任何做用,或者也可能使得某个线程的调度优先级高于其余线程,从而致使饥饿。code

一般,咱们尽可能不要改变线程的优先级。只要改变了线程的优先级,程序的行为就将与平台相关,而且会致使发生饥饿问题的风险。
    Thread.yield以及Thread.sleep的语义都是UB,JVM既能够将他们实现为空操做,也能够将它们视为线程调度的参考。
  1. 活锁(Livelock)

活锁是另外一种形式的活跃性问题,该问题不会致使线程阻塞,但也不能继续执行。由于线程将不断重复执行相同的操做,并且总会失败。 活锁一般发生在处理事务消息的应用程序中:若是不能成功处理某个消息,那么消息处理机制将回滚整个事务,并将它从新放到队列的开头。当多个相互协做的线程都对彼此进行响应从而修改各自的状态,并使得任何一个线程没法继续执行时,就发生了活锁,在并发应用程序中,经过等待随机长度的时间和回退能够有效避免活锁的发生。对象

相关文章
相关标签/搜索