3

我有一个与 Java 线程活锁相关的有趣问题。就这样吧。

有四个全局锁 - L1,L2,L3,L4

有四个线程 - T1、T2、T3、T4

T1 需要锁 L1,L2,L3 T2 需要锁 L2 T3 需要锁 L3,L4 T4 需要锁 L1,L2

因此,问题的模式是 - 任何线程都可以运行并以任何顺序获取锁。如果任何线程检测到它需要的锁不可用,它会释放它之前获得的所有其他锁,等待固定时间,然后再次重试。循环重复产生活锁条件。

所以,为了解决这个问题,我有两个解决方案

1)让每个线程在重试之前等待一段随机的时间。

OR,

2)让每个线程按特定顺序获取所有锁(即使一个线程不需要所有锁)

我不相信这是我唯一可用的两个选项。请指教。

4

4 回答 4

1

让所有线程在需要时进入单个受互斥体保护的状态机并释放它们的锁。线程应该公开方法,这些方法返回它们需要继续的锁集,并发出/等待私有信号量信号。SM 应该包含每个锁的布尔值和一个“等待”队列/数组/向量/列表/任何容器来存储等待线程。

如果一个线程进入 SM 互斥锁获得锁并且可以立即获得它的锁集,它可以重置它的布尔集,退出互斥锁并继续。

如果一个线程进入 SM 互斥体并且不能立即获得它的锁集,它应该将自己添加到“等待”,退出互斥体并等待它的私有信号量。

如果一个线程进入 SM 互斥锁以释放其锁,它会将锁布尔值设置为“返回”其锁并迭代“等待”以尝试找到一个现在可以使用可用锁集运行的线程。如果找到,它会适当地重置布尔值,从“等待”中删除它找到的线程并发出“找到”线程信号量的信号。然后它退出互斥锁。

您可以随意使用用于将可用的 set lock bools 与等待线程匹配的算法。也许您应该释放需要最大匹配集的线程,或者您可能想“旋转”“等待”容器元素以减少饥饿。由你决定。

像这样的解决方案不需要轮询(其性能消耗 CPU 使用和延迟),并且不需要连续获取/释放多个锁。

使用 OO 设计开发这样的方案要容易得多。发出信号/等待信号量并返回所需锁集的方法/成员函数通常可以填充在线程类继承链中的某个位置。

于 2013-04-22T15:33:54.910 回答
0

除非有充分的理由(性能方面)不这样做,否则我会将所有锁统一到一个锁对象上。这类似于您建议的解决方案 2,只是在我看来更简单。

顺便说一句,这个解决方案不仅更简单,更不容易出错,而且性能可能比您建议的解决方案 1 更好。

于 2013-04-22T14:21:53.867 回答
0

就个人而言,我从未听说过选项 1,但我绝不是多线程方面的专家。仔细想了想,感觉应该可以正常工作了。

但是,处理线程和资源锁定的标准方式与选项 2 有点相关。为了防止死锁,需要始终以相同的顺序获取资源。例如,如果您始终以相同的顺序锁定资源,则不会有任何问题。

于 2013-04-22T14:24:52.613 回答
0

使用 2a) 让每个线程以特定顺序获取它需要的所有锁(不是所有锁);如果线程遇到不可用的锁,则释放所有锁

只要线程以相同的顺序获取锁,就不会出现死锁;但是,您仍然可能处于饥饿状态(线程可能会遇到一种情况,即它不断释放所有锁而没有向前进展)。为了确保取得进展,您可以为线程分配优先级(0 = 最低优先级,MAX_INT = 最高优先级) - 当线程必须释放其锁时增加其优先级,并在它获取所有锁时将其降低到 0。将您的等待线程放入队列中,如果低优先级线程需要与高优先级线程相同的资源,则不要启动它 - 这样您就可以保证高优先级线程最终将获得所有锁。但是,除非您实际上遇到线程饥饿问题,否则不要实现此线程队列,因为它'

您还可以通过实施 omer schleifer 的 condense-all-locks-to-one 解决方案来简化事情;但是,除非您提到的四个线程之外的线程正在争夺这些资源(在这种情况下,您仍然需要从外部线程锁定资源),您可以通过删除所有锁并放置线程来更有效地实现这一点在一个循环队列中(所以你的线程只是保持以相同的顺序运行)。

于 2013-04-22T14:36:25.390 回答