前面谈了不少并发的特性和工具,可是大部分都是和锁有关的。咱们使用锁来保证线程安全,可是这也会引发一些问题。安全
最经典的锁顺序死锁就是LeftRightDeadLock.多线程
这个例子很简单,当两个线程分别获取到left和right锁时,互相等待对方释放其对应的锁,很显然双方都陷入了绝境。并发
与锁顺序死锁不一样的是动态的锁顺序死锁只是将静态的锁变成了动态锁。 一个比较生动的例子是这样的。工具
当咱们银行转帐的时候,咱们指望锁住双方的帐户,这样保证是原子操做。 看起来很合理,但是若是双方同时在进行转帐操做,那么就有可能发生死锁的可能性。spa
很显然,动态的锁顺序死锁的解决方案应该看起来和锁顺序死锁解决方案差很少。 可是一个比较特殊的解决方式是纠正这种顺序。 例如能够调整成这样:线程
这个挺有意思的。比较两个帐户的顺序,保证此两个帐户之间的传递顺序老是按照某一种锁的顺序进行的, 即便多个线程同时发生,也会遵循一次操做完释放完锁才进行下一次操做的顺序,从而能够避免死锁的发生。设计
资源死锁比较容易理解,就是须要的资源远远大于已有的资源,这样就有可能线程间的资源竞争从而发生死锁。 一个简单的场景是,应用同时从两个链接池中获取资源,两个线程都在等待对方释放链接池的资源以便可以同时获取 到所须要的资源,从而发生死锁。对象
资源死锁除了这种资源之间的直接依赖死锁外,还有一种叫线程饥饿死锁(thread-starvation deadlock)。 严格意义上讲,这种死锁更像是活跃度问题。例如提交到线程池中的任务因为老是不可以抢到线程从而一直不被执行, 形成任务的“假死”情况。blog
除了上述几种问题外,还有协做对象间的死锁以及开发调用的问题。这个描述起来会比较困难,也不容易看出死锁来。队列
一般发生死锁后程序难以自恢复。但也不是不能避免的。 有一些技巧和原则是能够下降死锁可能性的。
最简单的原则是尽量的减小锁的范围。锁的范围越小,那么竞争的可能性也越小。 尽快释放锁也有助于避开锁顺序。若是一个线程每次最多只可以获取一个锁,那么就不会产生锁顺序死锁。尽管应用中比较困难,可是减小锁的边界有助于分析程序的设计和简化流程。 减小锁之间的依赖以及遵照获取锁的顺序是避免锁顺序死锁的有效途径。
另外尽量的使用定时的锁有助于程序从死锁中自恢复。 例如对于上述顺序锁死锁中,使用定时锁很容易解决此问题。
看起来代码会比较复杂,可是这是避免死锁的有效方式。
对于多线程来讲,死锁是很是严重的系统问题,必须修正。除了死锁,遇到不少的就是活跃度问题了。 活跃度问题主要包括:饥饿,丢失信号,和活锁等。
饥饿是指线程须要访问的资源被永久拒绝,以致于不能在继续进行。 好比说:某个权重比较低的线程可能一直不可以抢到CPU周期,从而一直不可以被执行。
也有一些场景是比较容易理解的。对于一个固定大小的链接池中,若是链接一直被用完,那么过多的任务可能因为一直没法抢占到链接从而不可以被执行。这也是饥饿的一种表现。
对于饥饿而言,就须要平衡资源的竞争,例如线程的优先级,任务的权重,执行的周期等等。总之,当空闲的资源较多的状况下,发生饥饿的可能性就越小。
弱响应是指,线程最终可以获得有效的执行,只是等待的响应时间较长。 最多见的莫过于GUI的“假死”了。不少时候GUI的响应只是为了等待后台数据的处理,若是线程协调很差,颇有可能就会发生“失去响应”的现象。
另外,和饥饿很相似的状况。若是一个线程长时间独占一个锁,那么其它须要此锁的线程颇有可能就会被迫等待。
活锁(Livelock)是指线程虽然没有被阻塞,可是因为某种条件不知足,一直尝试重试,却终是失败。
考虑一个场景,咱们从队列中拿出一个任务来执行,若是任务执行失败,那么将任务从新加入队列,继续执行。假如任务老是执行失败,或者某种依赖的条件老是不知足,那么线程一直在繁忙却没有任何结果。
错误的循环引用和判断也有可能致使活锁。当某些条件老是不能知足的时候,可能陷入死循环的境地。
线程间的协同也有可能致使活锁。例如若是两个线程发生了某些条件的碰撞后从新执行,那么若是再次尝试后依然发生了碰撞,长此下去就有可能发生活锁。
解决活锁的一种方案是对重试机制引入一些随机性。例如若是检测到冲突,那么就暂停随机的必定时间进行重试。这回大大减小碰撞的可能性。
另外为了不可能的死锁,适当加入必定的重试次数也是有效的解决办法。尽管这在业务上会引发一些复杂的逻辑处理。