死锁

锁顺序死锁

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

动态的锁顺序死锁

如下例子, 是否死锁取决于参数顺序
image.png
改进代码:

  • 通过比较hashCode来定义锁顺序
  • 如果出现hash冲突, 则先由先获得tieLock锁来定义顺序

image.png

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

如下例子:

  • Taxi的setLocation需要先获得Taxi的锁, 再获得dispatcher的锁
  • Dispatcher的getImage需要先获得dispatcher的锁, 再获得taxi的锁

image.png

通过开放调用来避免在相互协作的对象之间产生死锁

使同步代码块仅被用于保护那些涉及共享状态的操作
image.png

资源死锁

正如当多个线程相互持有彼此正在等待的锁而又不释放自己已持有的锁时会发生死锁, 当它们在相同的资源集合上等待时, 也会发生死锁; 此外, 另一种死锁形式是线程饥饿死锁, 之前有讲.

死锁的避免与诊断

使用两阶段策略来检测代码中的死锁:

  1. 先找出在什么地方将获取多个锁
  2. 对所有这些实例进行全局分析, 从而确保它们在整个程序中获取锁的顺序都保持一致

支持定时的锁

显式使用Lock类的定时tryLock来代替内置锁机制, 如果超时, 可以释放这个锁, 然后后退并在一段时间后再次尝试, 从而消除了死锁发生的条件, 使程序恢复过来

其他活跃性危险

饥饿

当线程由于无法访问它所需要的资源而不能继续执行时, 就发生了饥饿.

  • 尽量不要改变线程的优先级, 只要改变了优先级, 线程的行为就与平台相关, 并且会导致发生饥饿问题的风险

糟糕的响应性

不良的锁管理也可能导致糟糕的响应性, 如果某个线程长时间占有一个锁, 而其他想要访问这个容器的线程就必须要等待很长时间

活锁

当多个相互协作的线程都对彼此进行响应从而修改各自的状态, 并使得任何一个线程都无法继续执行时, 就发生了活锁, 就像两个过于礼貌的人在半路上面对面相遇, 比此次都让出对方的路, 然而又在另一条路上相遇了, 因此他们就这样反复的避让下去

  • 要解决这种活锁问题, 需要在重试机制中引入随机性, 在并发应用程序中, 通过等待随机长度的时间和回退可以有效的避免活锁的发生