避免死锁的Java_Power Node Java Academy的组织

电脑杂谈  发布时间:2020-07-09 10:11:57  来源:网络整理

线程死锁_java线程死锁例子_java多线程避免死锁

在某些情况下,可以避免死锁. 本文将介绍三种避免死锁的技术:

1. 锁定顺序

2. 锁定时间限制

3. 死锁检测

锁定顺序

线程死锁_java线程死锁例子_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被释放. 然后他们必须成功锁定A才能尝试锁定B或C.

java多线程避免死锁_java线程死锁例子_线程死锁

顺序锁定是一种有效的防止死锁的机制. 但是,此方法要求您事先知道所有可能使用的锁(并对这些锁进行正确排序),但是有时这是不可预测的.

锁定时间限制

另一种避免死锁的方法是在尝试获取锁时添加超时期限. 这意味着,如果在尝试获取锁的过程中超过了此时间限制,则线程将放弃锁请求. 如果线程在给定的时间内未成功获取所有必需的锁,它将回滚并释放已获取的所有锁,然后等待随机的时间段再尝试. 这种随机的等待时间使其他线程有机会尝试获取相同的锁,并允许应用程序在未获取锁的情况下继续运行(在锁超时之后java多线程避免死锁,您可以继续运行以执行其他操作,然后继续执行)返回并重复之前的“锁定”逻辑. )

下面是一个示例,该示例显示了一个场景,其中两个线程尝试以不同的顺序获取相同的两个锁,先退后再在发生超时后重试:

 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.

线程死锁_java线程死锁例子_java多线程避免死锁

在上面的示例中,线程2重试以比线程1早200毫秒锁定,因此它可以首先成功地获得两个锁定. 此时,线程1尝试获取锁A并处于等待状态. 当线程2结束时,线程1也可以成功获得这两个锁(除非线程2或其他线程程1成功获得这两个锁之前获得了其中一些锁).

应该注意的是,由于锁定超时,我们不能假定在这种情况下发生了死锁. 这也可能是因为获得锁的线程(导致其他线程超时)需要很长时间才能完成其任务.

此外,如果有许多线程同时争用同一批资源,即使存在超时和回滚机制,也可能仍然导致这些线程反复尝试,但从未获得锁. 如果只有两个线程,并且重试超时设置在0到500毫秒之间,则可能不会发生这种现象,但是如果是10或20个线程,情况就不同了. 因为这些线程等待相等的重试时间的可能性要高得多(或者接近,以至于它们会引起问题). (超时和重试机制是为了避免同时发生竞争,但是当线程很多时,两个或多个线程的超时时间很可能相同或接近,因此即使存在竞争,超时稍后,因为超时时间相同,他们将同时开始重试,从而导致新一轮的竞争并带来新的问题. )

此机制存在问题. 您无法在Java中为同步块设置超时. 您需要创建自定义锁或使用Java 5中java.util.concurrent包下的工具. 编写自定义锁类并不复杂,但这超出了本文的范围.

死锁检测

java多线程避免死锁_线程死锁_java线程死锁例子

死锁检测是一种更好的防止死锁的机制. 主要针对无法实现顺序锁定且锁定超时不可行的场景.

每当线程获得锁时,它将被记录在与该线程和锁相关的数据结构(映射,图形等)中. 此外,每当线程请求锁定时,也需要将其记录在此数据结构中.

当线程未能请求锁时,线程可以遍历锁关系图以查看是否发生死锁. 例如,线程A请求锁7,但此时锁7由线程B持有. 这时,线程A可以检查线程B是否请求了线程A当前持有的锁定. 如果线程B确实有这样的请求,则发生了死锁(线程A拥有锁1并请求锁7;线程B拥有锁. 7java多线程避免死锁,并请求锁定1).

当然,死锁通常比两个线程持有彼此的锁要复杂得多. 线程A等待线程B,线程B等待线程C,线程C等待线程D,线程D再等待线程A. 为了使线程A检测死锁,它需要逐步检测B所请求的所有锁. 从线程B所请求的锁开始,线程A找到线程C,然后找到线程D,并找到线程D所请求的锁. 由线程A本身持有. 这是当它知道发生死锁的时候.

那么当检测到死锁时这些线程会做什么?

一种可行的方法是释放所有锁,回滚并等待随机的一段时间,然后重试. 这类似于简单的锁定超时. 区别在于它仅在发生死锁时回滚,而不是因为锁定请求超时. 尽管存在回滚和等待,但是如果有大量线程竞争同一批锁,它们仍将反复死锁.

一种更好的解决方案是为这些线程设置优先级,并让一个(或几个)线程回退. 其余线程继续保持所需的锁,就好像没有死锁发生一样. 如果分配给这些线程的优先级是固定的,则同一批线程将始终具有更高的优先级. 为避免此问题,可以在发生死锁时设置随机优先级.


本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/jisuanjixue/article-273983-1.html

    相关阅读
      发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

      热点图片
      拼命载入中...