`
85977328
  • 浏览: 1873346 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java并发(十一)避免死锁

 
阅读更多
在有些情况下死锁是可以避免的。本文将展示三种用于避免死锁的技术:

加锁顺序
加锁时限
死锁检测

加锁顺序

当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。

如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。看下面这个例子:

Thread 1:
  lock A
  lock B

Thread 2:
   wait for A
   lock C (when A locked)

Thread 3:
   wait for A
   wait for B
   wait for C
如果一个线程(比如线程3)需要一些锁,那么它必须按照确定的顺序获取锁。它只有获得了从顺序上排在前面的锁之后,才能获取后面的锁。

例如,线程2和线程3只有在获取了锁A之后才能尝试获取锁C(译者注:获取锁A是获取锁C的必要条件)。因为线程1已经拥有了锁A,所以线程2和3需要一直等到锁A被释放。然后在它们尝试对B或C加锁之前,必须成功地对A加了锁。

按照顺序加锁是一种有效的死锁预防机制。但是,这种方式需要你事先知道所有可能会用到的锁(译者注:并对这些锁做适当的排序),但总有些时候是无法预知的。

加锁时限

另外一个可以避免死锁的方法是在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行(译者注:加锁超时后可以先继续运行干点其它事情,再回头来重复之前加锁的逻辑)。

以下是一个例子,展示了两个线程以不同的顺序尝试获取相同的两个锁,在发生超时后回退并重试的场景:

Thread 1 locks A
Thread 2 locks B

Thread 1 attempts to lock B but is blocked
Thread 2 attempts to lock A but is blocked

Thread 1's lock attempt on B times out
Thread 1 backs up and releases A as well
Thread 1 waits randomly (e.g. 257 millis) before retrying.

Thread 2's lock attempt on A times out
Thread 2 backs up and releases B as well
Thread 2 waits randomly (e.g. 43 millis) before retrying.
在上面的例子中,线程2比线程1早200毫秒进行重试加锁,因此它可以先成功地获取到两个锁。这时,线程1尝试获取锁A并且处于等待状态。当线程2结束时,线程1也可以顺利的获得这两个锁(除非线程2或者其它线程在线程1成功获得两个锁之前又获得其中的一些锁)。

需要注意的是,由于存在锁的超时,所以我们不能认为这种场景就一定是出现了死锁。也可能是因为获得了锁的线程(导致其它线程超时)需要很长的时间去完成它的任务。

此外,如果有非常多的线程同一时间去竞争同一批资源,就算有超时和回退机制,还是可能会导致这些线程重复地尝试但却始终得不到锁。如果只有两个线程,并且重试的超时时间设定为0到500毫秒之间,这种现象可能不会发生,但是如果是10个或20个线程情况就不同了。因为这些线程等待相等的重试时间的概率就高的多(或者非常接近以至于会出现问题)。
(译者注:超时和重试机制是为了避免在同一时间出现的竞争,但是当线程很多时,其中两个或多个线程的超时时间一样或者接近的可能性就会很大,因此就算出现竞争而导致超时后,由于超时时间一样,它们又会同时开始重试,导致新一轮的竞争,带来了新的问题。)

这种机制存在一个问题,在Java中不能对synchronized同步块设置超时时间。你需要创建一个自定义锁,或使用Java5中java.util.concurrent包下的工具。写一个自定义锁类不复杂,但超出了本文的内容。后续的Java并发系列会涵盖自定义锁的内容。

死锁检测

死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。

每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。

当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生。例如,线程A请求锁7,但是锁7这个时候被线程B持有,这时线程A就可以检查一下线程B是否已经请求了线程A当前所持有的锁。如果线程B确实有这样的请求,那么就是发生了死锁(线程A拥有锁1,请求锁7;线程B拥有锁7,请求锁1)。

当然,死锁一般要比两个线程互相持有对方的锁这种情况要复杂的多。线程A等待线程B,线程B等待线程C,线程C等待线程D,线程D又在等待线程A。线程A为了检测死锁,它需要递进地检测所有被B请求的锁。从线程B所请求的锁开始,线程A找到了线程C,然后又找到了线程D,发现线程D请求的锁被线程A自己持有着。这是它就知道发生了死锁。

下面是一幅关于四个线程(A,B,C和D)之间锁占有和请求的关系图。像这样的数据结构就可以被用来检测死锁。



那么当检测出死锁时,这些线程该做些什么呢?

一个可行的做法是释放所有锁,回退,并且等待一段随机的时间后重试。这个和简单的加锁超时类似,不一样的是只有死锁已经发生了才回退,而不会是因为加锁的请求超时了。虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁(编者注:原因同超时类似,不能从根本上减轻竞争)。

一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。
  • 大小: 17.1 KB
分享到:
评论

相关推荐

    Java并发编程实战(中文版).pdf java多线开发

    死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用, 它们都将无法推进下去...标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。

    Java并发编程(学习笔记).xmind

    Java并发编程 背景介绍 并发历史 必要性 进程 资源分配的最小单位 线程 CPU调度的最小单位 线程的优势 (1)如果设计正确,多线程程序可以通过提高处理器资源的利用率来提升系统吞吐率 ...

    java高并发相关知识点.docx

    死锁:Java中的死锁,包括如何避免死锁和如何解除死锁。 性能优化:Java中的性能优化,包括JVM参数调优、代码优化、使用并发框架等。 并行计算:Java中的并行计算,包括Fork/Join框架、并行流等。 线程间通信:Java...

    java并发编程面试题

    java并发编程 基础知识,守护线程与线程, 并行和并发有什么区别? 什么是上下文切换? 线程和进程区别 什么是线程和进程? 创建线程有哪几种方式?,如何避免线程死锁 线程的 run()和 start()有什么区别? 什么是 ...

    Java并发编程实战

    Java并发编程实战 本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及...

    Java并发教程.md

    [并发](#并发) * [synchronized](#synchronized) * [synchronized底层原理](#synchronized底层原理) * [synchronized 使用方法](#... * [如何避免死锁?](#如何避免死锁) * [volatile](#volatile) * [volatile保

    java编写银行家算法

    1、 对死锁避免中的银行家算法作进一步理解。 2、 加深理解死锁的概念。 3、 加深理解安全序列和安全状态的概念。 4、 通过编程,掌握银行家算法分配资源的一步步实现过程。

    校招应用软件开发工程师(Java)

    C、更新数据时,先获取共享锁,再将共享锁升级为排他锁,可以避免死锁 D、锁既可以用于表级别,也可以用于行级别; 18、在TCP传输协议中,( D)可用于 流量控制。 A、FCS B、重传 C、计时器 D、滑动窗口

    一天搞定Java并发编程

    Java并发编程 一、并发编程的挑战 1.1 如何减少上下文切换 无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同的线程...

    此次课程设计的主要内容是模拟实现资源分配。同时要求编写和调试一个系统动态分配资源的简单模拟程序,观察死锁产生的条件,并使用适当的算法,有效的防止和避免死锁的发生。

    (1) 设计一个3个并发进程共享3类不同资源的系统,进程可动态地申请资源和释放资源,系统按各进程的申请动态地分配资源。 (2) 设计用银行家算法和随机分配算法,实现资源分配的两个资源分配程序,应具有显示或打印各...

    Java面试-彻底搞懂死锁的前世今生

    怎么避免死锁等问题展开分析,通过大量的代码和案例演示向大家描述死锁的前世今生。 快速到达看这里死锁是什么,有什么危害?写一个死锁的例子案例一:必然发生死锁案例二:两个账户转账案例三:多人多次转账发生...

    Java Pathfinder(JPF)完全安装配置过程(2013年6月)

    它的内核是一个Java虚拟机(JVM),可用于系统地探测程序所有可能的执行路径,以避免死锁或无法处理的异常之类的情况发生。与传统的调试程序不同,Java PathFinder报告导致缺陷的整个执行路径。Java PathFinder特别...

    【并发编程】自定义简单线程池.pdf

    书籍:如《Java并发编程实战》、《Concurrency in C++》等。 官方文档:不同编程语言的官方文档通常会提供关于并发编程的指南和最佳实践。 社区和论坛:如Stack Overflow、Reddit等,可以提供实际问题的帮助和讨论。

    【并发编程】深入理解JMM.pdf

    书籍:如《Java并发编程实战》、《Concurrency in C++》等。 官方文档:不同编程语言的官方文档通常会提供关于并发编程的指南和最佳实践。 社区和论坛:如Stack Overflow、Reddit等,可以提供实际问题的帮助和讨论。

    【并发编程】CAS到底是什么.pdf

    书籍:如《Java并发编程实战》、《Concurrency in C++》等。 官方文档:不同编程语言的官方文档通常会提供关于并发编程的指南和最佳实践。 社区和论坛:如Stack Overflow、Reddit等,可以提供实际问题的帮助和讨论。

    【并发编程】如何优雅使用线程池.pdf

    书籍:如《Java并发编程实战》、《Concurrency in C++》等。 官方文档:不同编程语言的官方文档通常会提供关于并发编程的指南和最佳实践。 社区和论坛:如Stack Overflow、Reddit等,可以提供实际问题的帮助和讨论。

    【并发编程】简单化理解AQS和ReentrantLock.pdf

    书籍:如《Java并发编程实战》、《Concurrency in C++》等。 官方文档:不同编程语言的官方文档通常会提供关于并发编程的指南和最佳实践。 社区和论坛:如Stack Overflow、Reddit等,可以提供实际问题的帮助和讨论。

    【并发编程】volatile的原理我好像又懂了.pdf

    书籍:如《Java并发编程实战》、《Concurrency in C++》等。 官方文档:不同编程语言的官方文档通常会提供关于并发编程的指南和最佳实践。 社区和论坛:如Stack Overflow、Reddit等,可以提供实际问题的帮助和讨论。

    DiningPhilosophers:经典的并发问题,用Java解决

    餐饮哲学家经典的并发问题,用Java解决避免死锁、活锁和资源匮乏。

    Java开发手册-代码规范

    并发编程:包括多线程编程的规范,如何正确使用锁、避免死锁等。 安全性:包括如何防止代码被恶意攻击、如何处理敏感信息等方面的安全规范。 性能优化:包括如何优化代码的执行效率、减少资源消耗等方面的性能优化...

Global site tag (gtag.js) - Google Analytics