深入了解Java并发——《Java Concurrency in Practice》10.避免活跃性危险

Posted 在咖啡里溺水的鱼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入了解Java并发——《Java Concurrency in Practice》10.避免活跃性危险相关的知识,希望对你有一定的参考价值。

死锁是并发编程中最容易遇到的活跃性问题,本章就死锁发生的原因、诊断、避免死锁的方案进行了细致的解读。

10.1 死锁

在线程A持有锁L并想获得锁M的同时,线程B持有锁M并尝试获得锁L,那么这两个线程将永远的等待下去。这种情况是最简单的死锁形式,称为 抱死 Deadly Embrace。

数据库系统的设计中考虑了检测死锁以及从死锁中恢复。当检测到一组事务发生了死锁时,将选择一个牺牲者并放弃这个事务。JVM并没有这样的处理机制。

10.1.1 锁顺序死锁

两个线程视图以不同的顺序来获得相同的锁而引发的死锁称为锁顺序死锁

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

10.0.2 动态的锁顺序死锁

执行的时序不当,可能会导致看起来加锁顺序相同的操作也产生死锁。

在指定锁顺序时,可以使用System.identityHashCode方法,该方法将返回由Object.hashCode返回的值。

极少数情况下,两个对象可能拥有相同的散列值,为了避免这种情况,可以使用 加时赛 TieBreaking 锁。在获得两个锁之前,首先获得 TieBreaking锁,从而保证每次只有一个线程以未知的顺序获得者两个锁,从而消除死锁发生的可能性。

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

如果在持有锁时调用某个外部方法,那么将出现活跃性问题。在这个外部方法中可能会获取其他锁,这可能会导致死锁,或者阻塞事件过长,导致其他线程无法及时获得当前被持有的锁。

所以持有锁时,应尽量避免调用外部方法。同样也就是要减少锁的范围。

10.1.4 开放调用

调用某个方法时不需要持有锁的调用称为开放调用 Open Call

依赖于开放调用的类通常能表现出更好的行为,在**程序中应尽量使用开放调用。与那些在持有锁时调用外部方法的程序相比,更容易对依赖于开放调用的程序进行死锁分析。

10.1.5 资源死锁

多个线程在相同的资源集合上等待时,也会发生死锁。

有界线程池/资源池与相互依赖的任务不能一起使用。

10.2 死锁的避免与诊断

如果一个程序每次至多只能获得一个锁,就不会产生锁顺序死锁。如果必须获得多个所,那么设计时必须考虑锁的顺序:尽量减少潜在的加锁交互数量,将获取锁时需要遵循的协议写入正式文档并始终遵循这些协议。

在使用细粒度锁的程序中,可以通过使用一种两阶段策略 Two-Part Strategy 来检查代码中的死锁:首先,找出在什么地方将获得多个锁,然后对所有这些实例进行全局分析,从而确保它们在整个程序中获取锁的顺序都保持一致。尽可能的使用开放调用能够极大的简化分析过程。

10.2.1 支持定时的锁

显式使用Lock类中的定时trylock功能来代替内置锁机制能够检测死锁和从死锁中恢复。显示锁可以指定一个Timeout,在超时后tryLock会返回一个失败信息。如果Timeout比获取锁的时间要长的多,就可以在发生某个以外情况后重新获得控制权。

当定时锁失败时,并不需要知道失败的原因,但是通过这种方式可以记录发生的失败,以及其他有用的信息,并通过一种更加平缓的过程重新启动计算。

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

JVM会通过线程转储 Thread Dump来帮助识别死锁的发生。要在UNIX平台上触发线程转储操作,可以通过向JVM进程发送SIGQUIT信号(kill -3 或 Ctrl-\\),Window平台上Ctrl-Break。

10.3 其他活跃性危险

10.3.1 饥饿

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

要避免使用线程优先级,因为这会增加平台的依赖性,并可能导致活跃性问题。在大多数并发应用程序中,都可以使用默认的线程优先级。

10.3.2 糟糕的响应性

10.3.3 活锁

活锁 Livelock不会阻塞线程,但也不能继续执行,因为线程将不断的重复执行相同的操作,而且总会失败。通常是因为过度的错误恢复代码造成的,因为它错误的将不可修复的错误作为可修复的错误。

当多个相互协作的线程都对彼此进行响应从而修改各自的状态,并使得任何一个线程都无法继续执行时也会发生活锁。就像两个礼貌的人不停的互相让路。要解决这种活锁问题,需要在重试机制中引入随机性。

以上是关于深入了解Java并发——《Java Concurrency in Practice》10.避免活跃性危险的主要内容,如果未能解决你的问题,请参考以下文章

深入了解Java并发——《Java Concurrency in Practice》14.构建自定义的同步工具

基于JVM原理JMM模型和CPU缓存模型深入理解Java并发编程

深入了解Java并发——《Java Concurrency in Practice》10.避免活跃性危险

深入了解Java并发——《Java Concurrency in Practice》11.性能与可伸缩性

深入了解Java并发——《Java Concurrency in Practice》8.线程池的使用

一文让你深入了解 Java-Netty高性能高并发