在 Java 中断言同步顺序

在高度并发的系统中,很难确信锁的使用是正确的。具体来说,如果以在另一个线程中以正确顺序获取时未预期的顺序获取锁,则可能导致死锁。

有一些工具(例如Coverity)可以在代码库上进行静态分析,并寻找“不寻常的”锁定订单。我想探索其他选项来满足我的需求。

是否有任何轻量级*工具用于检测Java代码,可以检测以不同于预期的顺序获取锁的情况?我可以通过评论/注释明确调用锁定订单。

首选免费和/或开源解决方案。如果存在解决此问题的非检测方法,也请进行注释。

*对于我的目的,轻量级意味着...

  • 如果是检测,我仍然可以以相同的性能运行我的程序。我想,30-50%的降解是可以接受的。
  • 我不必花半天时间与该工具进行交互,只是为了从中获得“好”效果。理想情况下,我应该只注意到我在出现问题时才使用它。
  • 如果是检测,则对于生产环境应该很容易禁用。
  • 它不应该在每个语句中都使我的代码混乱。如前所述,我可以明确地注释/注释使用相对顺序锁定的对象或对象类。synchronize

答案 1

我没有使用AspectJ,所以不能保证它有多容易使用。我使用ASM创建自定义代码探查器,这大约需要2天的工作。检测同步的工作量应该是相似的。AspectJ应该更快,更容易,一旦你跟上了各个方面的速度。

我已经为基于c ++的服务器实现了死锁检测跟踪。以下是我是如何做到的:

  • 当获取或释放锁时,我跟踪:
    • <time> <tid> <lockid> <acquiring|releasing> <location in code>
  • 这种额外的跟踪极大地影响了性能,并且在生产中不可用。
  • 因此,当在生产环境中发现可能的死锁时,我使用日志文件来确定死锁周围发生了什么。然后在打开跟踪的测试环境中重现此功能。
  • 然后,我在日志文件上运行了一个脚本,以查看死锁是否可行以及如何实现。我使用了一个awk脚本,使用了这个算法:
    • 前线
      • 如果获取
        • 将 lockid 添加到此线程的当前锁列表中
        • 将此列表中的每对锁添加到此线程的设置锁对中。例如,用于生成对的列表Lock A -> Lock B -> Lock C(Lock A, Lock B), (Lock A, Lock C), (Lock B, Lock C)
      • 如果释放
        • 从此线程的列表尾部删除当前 lockid
    • 对于每个锁对,在所有其他线程中搜索反向锁对,每个匹配项都是一个潜在的死锁,因此请打印受影响的锁对和线程
    • 我没有让算法更智能,而是检查了锁的采集,看看它是否是真正的死锁。

在几天内未能找到死锁的原因后,我这样做了,又花了几天时间实施,花了几个小时才找到死锁。

如果你正在考虑在Java中采用这种方法,需要考虑的事情是:

  • 您是否仅用于保护关键部分?您是否正在使用 java.lang.concurrent 中的类?(这些可能需要特殊的处理/仪器)synchronized
  • 使用方面/ASM打印代码位置的难易程度如何?我用和在c ++中。ASM 将为您提供类名、方法名和签名。__FILE____LINE__
  • 不能检测用于保护跟踪/日志记录的锁。
  • 如果对文件对象使用每个线程的日志文件和线程本地存储,则可以简化检测。
  • 如何唯一标识同步对象?也许toString()和System.identityHashCode()就足够了,但可能需要更多。我在C++中使用了对象的地址。

答案 2

您可以使用AspectJ,它相对容易学习,并且允许您设置自己的自定义和简化方法来监视线程及其访问的任何锁。


推荐