10.1 死锁

10.1.1 锁顺序死锁 - LeftRightDeadlock

  1. 如果所有线程以固定的顺序来获得锁,那么在程序中就不会出现顺序死锁问题

10.1.2 动态的锁顺序死锁 - DynamicOrderDeadlockDemonstrateDeadlock

10.1.3 在协作对象之间发生的死锁 - CooperatingDeadlock

  1. 如果在持有锁时调用某个外部方法, 那么将出现活跃性问题。在这个外部方法中可能会获得其他锁(这可能会死锁),或者阻塞时间过长,导致其他现场无法及时获得当前被持有的锁。<br />

10.1.4 开放调用 - CooperatingNoDeadlock

10.1.5 资源死锁

当多个线程相互持有彼此正在等待的锁而又不释放自己已持有的锁时会发生死锁,当它们在相同的资源上等待时,也会发生死锁。
另一种基于资源的死锁形式-线程饥饿死锁 8.1.1节:
一个任务提交另一个任务,并等待被提交任务在单线程的Executor中执行完成。这时,第一个任务将永远等待下去,并使得另一个任务以及在这个Executor中执行的所有其他任务都停止执行。 有界线程池/资源池与相互依赖的任务不能一起使用。

10.2 死锁的避免与诊断

  • 尽量使一个程序每次只能获得一个锁
  • 如果必须获取多个锁,在设计时考虑锁的顺序、减少前缀的加锁交互数量、将获取锁时需要遵循的协议写入正式文档并遵循

使用两阶段策略检查细粒度锁的死锁

  • 找出获取多个锁的地方(使这个集合尽量小)
  • 分析实例,确保在整个程序中获取锁的顺序一致

尽可能使用开放调用

10.2.1 支持定时的锁

显示使用Lock类中的定时tryLock(13章)的功能代替内置锁
显示锁可以指定超时时限(Timeout),超时后tryLock会返回一个失败信息(而内置锁会永远等待下去)

10.2.2 通过线程转储信息来分析死锁

推荐使用线程转储找出死锁的位置,编辑器也对其提供支持

10.3 其他活跃危险

包括饥饿、丢失信号和活锁。(丢失信号在14.2.3节介绍)

10.3.1 饥饿(Starvation)

饥饿是指线程因无法访问它所需要的资源而不能继续执行。 要避免使用线程优先级,因为这会增加平台依赖性,并可能导致活跃性问题。在大多数并发应用程序中,都可以使用默认的线程优先级。

10.3.2 糟糕的响应性

不良的锁管理可能导致糟糕的响应性。

10.3.3 活锁(Livelock)

活跃性问题的一种形式,该问题不会阻塞线程,但也不能继续执行。 当多个相互协作的线程都对彼此进行响应从而修改各自的状态,并使得任何一个线程都无法继续执行时,就发生了活锁。
例如;两个过于礼貌的人在半路上面对面相遇:他们都彼此让出对方的路,然而又在另一条路上相遇了,因此他们就这样反复地避让下去。
活锁通常发生在事务消息中:从队头取出一个消息,发现不能成功处理该消息,回滚整个事务,并将消息重新放入队头。因为重新被放入队头,处理器将反复调用,并返回相同的结果。这样就无法继续执行下去了。
解决方案:在重试机制中引入随机性。

  • 并发程序中,等待随机长度的时间和回退可以有效避免活锁的发送。
  • 在网络上,两台机器检查到冲突,需让他们分别等待一段随机时间后,再重发。

    小节

    锁顺序死锁 (常见活跃性故障)
    确保在获取多个锁时采用一致的顺序。
    在程序中始终使用开放调用。