Java并发编程学习笔记(十四)避免死锁 比眉伴天荒 2022-05-30 01:18 183阅读 0赞 在有些情况下死锁是可以避免的。本文将展示三种用于避免死锁的技术: 1. [加锁顺序][Link 1] 2. [加锁时限][Link 2] 3. [死锁检测][Link 3] #### 加锁顺序 #### 当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。 如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。看下面这个例子: 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)之间锁占有和请求的关系图。像这样的数据结构就可以被用来检测死锁。 [![deadlock-detection-graph.png][]][deadlock-detection-graph.png 1] 那么当检测出死锁时,这些线程该做些什么呢? 一个可行的做法是释放所有锁,回退,并且等待一段随机的时间后重试。这个和简单的加锁超时类似,不一样的是只有死锁已经发生了才回退,而不会是因为加锁的请求超时了。虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁(*编者注:原因同超时类似,不能从根本上减轻竞争*)。 一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。 -------------------- 扩展阅读:*译者注* * 《现代操作系统 第三版》§6.2~§6.7 **原创文章,转载请注明:** 转载自[并发编程网 – ifeve.com][_ ifeve.com]**本文链接地址:** [避免死锁][Link 4] [Link 1]: http://ifeve.com/deadlock-prevention/#ordering [Link 2]: http://ifeve.com/deadlock-prevention/#timeout [Link 3]: http://ifeve.com/deadlock-prevention/#detection [deadlock-detection-graph.png]: /images/20220530/6ab24723fc6e4d37a43e836910b54583.png [deadlock-detection-graph.png 1]: http://ifeve.com/?attachment_id=3873 [_ ifeve.com]: http://ifeve.com/ [Link 4]: http://ifeve.com/deadlock-prevention/
相关 Java并发编程挑战:死锁避免策略 在Java并发编程中,死锁是一个常见的问题。为了避免死锁,通常会遵循以下几种策略: 1. **资源有序性**(Resource Order): - 每个线程请求的资源必 迷南。/ 2024年09月27日 00:51/ 0 赞/ 56 阅读
相关 Java并发编程:死锁与避免策略 在Java并发编程中,死锁是一种严重的问题,当两个或更多的线程互相等待对方释放资源时,就可能发生死锁。 避免死锁的策略主要包括: 1. **资源有序分配**:确保所有线程按 £神魔★判官ぃ/ 2024年09月24日 04:15/ 0 赞/ 69 阅读
相关 Java并发编程:如何避免死锁问题 死锁是多线程编程中的一种严重问题,当两个或更多的线程在执行过程中因争夺资源而造成的一种互相等待的现象称为死锁。 为了避免死锁,可以遵循以下策略: 1. **预防死锁**: - 日理万妓/ 2024年09月16日 10:18/ 0 赞/ 77 阅读
相关 Java并发编程:死锁避免策略 在Java的并发编程中,死锁是一种常见的问题。死锁是多个进程或线程因争夺资源而造成的一种互相等待的现象,若无外力干涉,它们都将无法继续执行。 为了避免死锁,通常采用以下几种策 ╰+攻爆jí腚メ/ 2024年09月13日 04:15/ 0 赞/ 69 阅读
相关 Java并发编程中如何避免死锁 在Java并发编程中,死锁是一种严重的性能问题,它发生在两个或更多的线程互相等待对方释放资源的情况下。 以下是一些避免死锁的策略: 1. 避免嵌套锁:一个线程获取了多个锁后 矫情吗;*/ 2024年09月11日 16:09/ 0 赞/ 91 阅读
相关 Java并发编程:死锁和避免死锁的方法 死锁是多线程编程中的一种严重问题,会导致所有线程都无法继续执行任务。以下是死锁的特征和一些避免死锁的方法: 1. **特征**: - **互斥**:资源被多个线程同时请 待我称王封你为后i/ 2024年09月11日 05:42/ 0 赞/ 67 阅读
相关 Java并发编程学习笔记(十四)避免死锁 在有些情况下死锁是可以避免的。本文将展示三种用于避免死锁的技术: 1. [加锁顺序][Link 1] 2. [加锁时限][Link 2] 3. [死锁检测][Link 比眉伴天荒/ 2022年05月30日 01:18/ 0 赞/ 184 阅读
相关 Java并发编程学习笔记(十三)死锁 死锁是两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多个线程同时但以不同的顺序请求同一组锁的时候。 例如,如果线程1锁住了A,然后尝试对B进行加锁, 矫情吗;*/ 2022年05月30日 01:12/ 0 赞/ 261 阅读
还没有评论,来说两句吧...