避免活跃性

Posted 老人与JAVA

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了避免活跃性相关的知识,希望对你有一定的参考价值。

10 避免活跃性

在安全性与活跃性之间通常存在着某种制衡。例如加锁导致死锁,或者使用线程池和信号量来限制对资源的使用,但这些被限制的行为可能会导致资源死锁。、

10.1 死锁

当一个线程永远地持有一个锁,并且其他线程都尝试获得这个锁时,那么它们将永远被阻塞。

 

10.1.1 锁顺序死锁

如果用固定的顺序来获取锁,就不会发生死锁。

 

10.1.2动态的锁顺序死锁解决方案

其中一个线程从X向Y转账,另一个线程从Y向X转账,就会发生死锁:

A:transferMoney(myACCOUnt,yourACCOUnt,10)

B:transferMoney(yourACCOUnt,myACCOUnt,20)

于我们无法控制参数的顺序,因此要解决这个问题,必须定义锁的顺序。在制定锁的顺序时,可以使用System.identityHashCode方法,该方法将返回由Object. hashCode返回的值。如果返回的hash值相同(小概率),则采用加时赛锁tieLock

 

10.1.3开放调用

什么是协作对象之间的死锁?

在持有锁的情况下,调用外部(不了解)的方法,容易出现活跃性问题。在这个外部方法中可能会出现其他锁,或者长时间阻塞,导致当前持有的锁不能被其他线程获得。

用例:

因为setLocation和notifyAvailable都是同步方法,因此调用setL.ocation的线程将首先获取Taxi的锁,然后获取Dispatcher的锁。同样,调用getImage的线程将首先获取Dispatcher锁。然后再获取每一个Taxi的锁(每次获取一个)。产生死锁。

 

什么是开放调用原则?

即在调用某个方法时不需要持有锁。这样可以避免协作死锁,编码和分析安全性都变得简单

 

 

10.1.4 资源死锁

如果一个任务需要连接两个数据库。那么线程A可能持有与数据库D1的连接,并等待与数据库D2的连接,而线程B则持有与D2的连接并等待与D1的连接。

 

另一种基于资源的死锁形式就是线程饥饿死锁。如果某些任务需要等待其他任务的结果,那么这些任务往往是产生线程饥饿死锁的主要来源,所以有界线程池/资源池与相互依赖的任务不能一起使用。

 

 

10.2 死锁的避免与诊断

10.2.1 基本方式

A:将锁的使用顺序写入文档,避免死锁

B:可以通过代码审查,或者借助自动化的源代码分析工具检查死锁

C:遵循开放调用原则

 

10.2.2 显示锁的定时功能

使用显示锁Lock的定时功能(tryLock)代替内置锁机制。当使用内置锁时,只要没有获得锁,就会永远等待下去,而显式锁则可以指定一个超时时限(Timeout),在等待超过该时间后tryLock会返回一个失败信息。当定时锁失败时,你并不需要知道失败的原因。但是你记录了这次操作的其他有用信息。并通过一种更平缓的方式来重新启动计算,而不是关闭整个进程。

即使在整个系统中没有始终使用定时锁,使用定时锁来获取多个锁也能有效地应对死锁问题。如果在获取锁时超时,那么可以释放这个锁,然后后退并在一段时间后再次尝试,从而消除了死锁发生的条件,使程序恢复过来。(这项技术只有在同时获取两个锁时才有效,如果在嵌套的方法调用中请求多个锁,那么即使你知道已经持有了外层的锁,也无法释放它。)

10.2.3 线程转储

线程转储包括各个运行中的线程的栈追踪信息,这类似于发生异常时的栈追踪信息。例如每个线程持有了哪些锁,在哪些栈帧中获得这些锁,以及被阻塞的线程正在等待获取哪一个锁。在生成线程转储之前,JVM将在等待关系图中通过搜索循环来找出死锁。

虽然Java 6中包含对显式Lock的线程转储和死锁检测等的支持,但在这些锁上获得的信息比在内置锁上获得的信息精确度低。内置锁与获得它们所在的线程栈帧是相关联的,而显式的Lock只与获得它的线程相关联。

 

10.3 其他活跃性危险

10.3.1 慎用thread的优先级

JVM根据需要将thread线程的优先级映射到操作系统的调度优先级。但是在Thread API中定义10个优先级,不同的操作系统的优先级却不同,可能少于10个,这样就造成不同优先级的线程映射为相同的优先级,使线程的优先级失去了意义。所以慎用thread优先级,尽量用Thread.sleep或Thread.yield。

 

10.3.2糟糕的响应性

CPU密集型的后台任务仍然可能对响应性造成影响,因为它们会与事件线程共同竞争CPU的时钟周期。

 

10.3.3 活锁

要解决这种活锁问题,需要在重试机制中引人随机性。

例如,在网络上,如果两台机器尝试使用相同的载波来发送数据包,那么这些数据包就会发生冲突。这两台机器都检查到了冲突,并都在稍后再次重发。如果二者都选择了在1秒钟后重试,那么它们又会发生冲突,并且不断地冲突下去,因而即使有大量闲置的带宽,也无法使数据包发送出去。为了避免这种情况发生,需要让它们分别等待一段随机的时间。

以上是关于避免活跃性的主要内容,如果未能解决你的问题,请参考以下文章

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

《Java并发编程实战》第十章 避免活跃性危急 读书笔记

《Java并发编程实战》第十章 避免活跃性危急 读书笔记

如何避免在jQuery中重复类似的代码?

节假日网络攻击事件高发 企业如何避免损失?

应用进入后台时避免外接显示器显示原生 iOS 屏幕