死锁()

安全性和活跃度通常相互牵制。我们使用锁来保证线程安全,但是滥用锁可能引起锁顺序死锁(lock-ordering deadlock)


死锁:当一个线程永远占用一个锁,而其他线程尝试去获得这个锁,那么他们将永远被阻塞。当线程A占有锁L时,想要获得锁M,但同时,线程B持有M,并尝试获得L,两个线程将永远等待下去。这种情况是死锁的最简单形式(或称致命拥抱,deadly embrace),发生在多个线程因为环路的锁依赖关系而永远等待的情况下。(把这些线程假象为有向图的节点,如果图连成一个环图,那么死锁就产生了)。


资源死锁:

避免死锁的方法:如果一个程序一次最多获得一个锁,那么就不会产生锁锁顺序死锁。当然,这通常并不现实,但是日光你能够避免这个情况,就能省去很多工作。如果你必须获得多个锁,那么锁的顺序必须是你设计工作的一部分:尽量减少锁之间的交互数量,遵守并文档化锁的顺序,这些缺一不可。

  在使用定义良好的锁的程序中,监测代码中死锁自由度的策略分为两个部分:首先识别什么地方会获得多个锁(使这个集合尽量小),对这些示例进行全局的分析,确保它们锁的顺序在程序中保持一致,尽可能的使用开发调用,这样做能够从根本上简化分析的难度。在没有非看法调用的程序中,发现那些获得多重锁的实例是非常简单的,既可以通过代码,也可以通过对字节码或源码自动进行分析来完成。


通过jvm线程转储分析死锁

jvm线程转储(thread dump)包括运行中的线程栈追踪信息,以及与之相似并随之方式的异常,也包括锁的信息,比如:那个锁用哪个线程获得,其中锁的栈结构,以及阻塞线程正在等待的锁究竟是哪一个。在生成线程存储钱,jvm表示“正在等待(is-waiting-for)”关系的(有向)图中搜索循环来寻找死锁。如果发现了死锁,它就会包括死锁的识别信息,其中参与了哪些锁和线程,以及程序中造成不良后果的锁的请求发生在哪里。


如果你使用了显示的Lock类,而不是内部锁,java5.0并不支持与Lock相关的转储信息,显示的Lock并不会直接获得转储。java6支持但是信息要比内部锁少很多。内部锁与获得他的线程的栈框架相关联;显式锁Lock只会与获得它的线程相关联。


其他活跃度的危险:

尽管死锁是我们遇到的最主要的活跃度危险,并发程序中仍然肯遇到一些其他的活跃度危险,包括:饥饿,丢失信号和活锁。

      1、饥饿:当线程访问它所需要的资源时却被永久拒绝,以至于不能在继续进行,这要就发生了饥饿(starvation);

       最常见的应付饥饿的资源是CPU周期。咋java应用程序中,使用线程的优先级不当肯能会引起饥饿。在锁中执行无休止的构建也肯能英气饥饿(无限循环,或无尽的等待资源),因为其他需要这个锁的线程永远不能得到它。

   注意:不要用线程优先级,只要你开始改变线程的优先级,程序的行为就变为与平台相关了。并且你会引入饥饿发生的风险。你经常能够范县程序试图从修改了优先级的问题中恢复,也可能是某些其他地方,为了尝试给低优先级线程分配更多的时间,偶然调用了SLEEP或YIELD造成的响应性为题

 2、弱响应性

   不良的锁管理也可能引起弱响应性。如果一个线程长时间占有一个锁(可能正在对一个大容器进行迭代,并对每一个元素进行耗时的工作),其他想要访问该容器的线程就必须等待很长时间。

3、活锁

 活锁(livelock)是线程中活跃失败的另一种形式,尽管没有被阻塞,线程却仍然不能继续,因为它不断重试相同的操作。却总是失败。

活锁通常发生在消息处理应用程序中,如果消息处理失败,传递消息的底层架构会回退整个事物,并把它置回队首。如果消息处理程序对某种特定类型的消息处理存在bug,每次都处理失败,那么每一次这个消息都会被重队列取出,存在问题在事物回退放在队列首,反复结果。这个就是通常称为毒药信息(poison message)的问题。

活锁同样发生在多个相互协作的线程间,当它们为了彼此间响应而修改的状态,使得没有一个线程能够继续前进,那么就发生了活锁。这好比两个过于里面的人咋半路相遇,他们都避开的对方的路,于是在另一条路上又相遇了。所以就这样不停地一直避让下去。

解决这样的活锁问题的一种方案就是对重试机制引入一些随机性来避免。

总结:活跃度失败是非常严重的问题,因为除了短时间地终止应用程序,没有任何机制可以恢复这种失败。最常见的活跃度失败是锁顺序死锁。应该在设计时就避免锁顺序死锁:确保多个线程在获得多个锁时,使用一致的顺序。最好的解决办法是在程序中使用开放调用。这会大大减少一个线程一次请求多个锁的情况,并且是这样的多重锁请求的发生更加明显





你可能感兴趣的:(死锁())