【问题标题】:Fixing deadlocks in messy code修复混乱代码中的死锁
【发布时间】:2012-04-06 12:23:21
【问题描述】:

我正在修改一些非常复杂的代码,我需要在此基础上添加自己的同步。

但是,现有代码有大约十几个(如果不是更多的话)不同的锁,我的​​代码需要调用它的一些方法。我真的不知道获取锁的顺序,也无法真正控制。

所以,我的问题是,如果我用一个锁替换所有不同的锁会发生什么?。除了牺牲粒度之外,还有什么我应该注意的问题吗?

谢谢!

【问题讨论】:

  • 能发一下代码的sn-p吗?
  • 不,这就像 3000+ 行。 :-D 这不是我的代码,请注意...
  • 据我了解,如果你需要所有方法的原子性,你需要一个锁,如果你想要公平,这样每个线程将按照他们寻求的顺序由处理器提供服务资源,你可以看一下 java.util.concurrent.Lock (它保证了公平性,但它带来了开销)。请阅读 ibm.com/developerworks/java/library/j-jtp09238/index.htmlibm.com/developerworks/java/library/j-jtp10264 以更好地了解锁和在 java 中的先发生顺序。
  • 上周已经深入研究了不属于我的多线程代码,我可以告诉你,这并不容易,你可能需要花时间真正了解这段代码。在不了解代码当前如何工作的情况下,我会犹豫盲目地更换锁。幸运的是,JVM 非常擅长检测死锁,所以如果您还没有学习过,您应该学习如何捕获和读取线程转储。最后,在没有看过代码的情况下,我会说你绝对应该更喜欢 java.util.concurrent 中的工具,而不是任何自制的锁定解决方案。

标签: java multithreading synchronization locking


【解决方案1】:

如果您更改所有 synchronized 块(和方法),所有其他阻塞结构,我认为您应该没问题 - 最坏的情况是,您的应用程序退化为串行执行.但是如果你只改变其中的一些,你可能会陷入僵局。考虑两个线程各自获取多个锁的场景:

Thread 1:
    synchronized A
        synchronized B

Thread 2:
    synchronized B
        synchronized C

这里没有死锁的风险,但是如果您将AC(但不是B)替换为新的通用锁,那么您将拥有:

Thread 1:
    synchronized L
        synchronized B

Thread 2:
    synchronized B
        synchronized L

...这是典型的死锁案例。

考虑另一种情况,锁本身不提供死锁,而是死锁像 CountDownLatch 这样的阻塞类:

Thread 1:
    synchronized A
        latch L.countDown()

Thread 2:
    synchronized B
        latch L.await()

在这种情况下,将两个 synchronized 块更改为锁定在一个公共锁上不会导致它们之间的死锁,但如果线程 2 先获得锁,则会导致死锁:它将等待锁存器的倒计时,这永远不会因为线程 1 在其 synchronized 入口点被阻塞。此示例也适用于其他阻塞结构:信号量、阻塞队列等。

【讨论】:

  • 倒计时锁的重点是——如果您更换锁,您还必须确保在等待公共资源时释放锁(如果尚未完成)。
  • 谢谢@yshavit!但是,我不明白最后一个例子。闩锁对我来说是新事物,所以如果我在代码中看不到闩锁,我不应该打扰,不是吗?或者你的意思是latch 更抽象的东西?谢谢!
  • @AlbusDumbledore 正如@Alex 指出的那样,它实际上是任何可以阻塞的资源。 java.util.concurrentjava.util.concurrent.locks 中的许多类都适用,但原则上它可以是其他资源(例如一段代码创建文件,另一段代码等待该文件存在)。
【解决方案2】:

我认为没有任何替代方法可以正确分析代码。它可怕的原因可能是因为其他不得不修改它的人都做了和你完全一样的事情,并且对正确的分析犹豫不决。

编写一些日志代码来阐明锁定应该是相当简单的。一旦您可以拆开层并获得清晰的画面,用一把现代锁(例如ReadWriteLock 或类似的锁)替换整个地块应该相对简单。

您可能会发现利用这个机会添加一些测试代码以控制您的日志记录很有用。对任何复杂代码都非常有用。

【讨论】:

    【解决方案3】:

    如果没有看到代码,很难确切地说会发生什么。 如果在其中一个锁内,它试图抓住另一个锁,您可能会遇到死锁问题。此外,在等待获取单个锁与多个锁时,它可能会减慢应用程序的速度。

    【讨论】:

    • 嗯,我的想法是,如果我将所有锁合并为一个,它将摆脱牺牲粒度的死锁问题。我想知道我是否正确。
    • @AlbusDumbledore 如果锁定是唯一发生的同步,那么您可能是对的。请理解,通过牺牲粒度,您也可能会牺牲性能并引入饥饿的机会。
    • 谢谢亚历克斯。我对可能的性能问题没有意见,事实是,大多数方法花费的时间非常非常短(例如不到几毫秒),同时调用太多线程的可能性很小,但另一方面,代码的复杂性是巨大的,但我不明白为什么他们使用这么多锁引入了更多的复杂性。非常感谢您确认我的想法。
    猜你喜欢
    • 1970-01-01
    • 2013-05-27
    • 1970-01-01
    • 1970-01-01
    • 2016-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多