更新:
当我第一次发布这个时,我相当确定代码被破坏了。现在,我不再确定我观察到了什么。我遇到的最大问题是我似乎无法应用 17.4. Memory Model 并直接说明它是否应该工作。

以下代码已损坏。

它试图实现的目标过于复杂,但此外,它是线程不安全的,因为我观察到它可以无限期地等待 c 。我不担心前者(可以使用 ReentrantLockCountDownLatch 来表示更清晰的代码),但我想知道,后者的原因是什么?

static final ConcurrentHashMap<Integer, Object> mutex = new ConcurrentHashMap<>();


public static brokenFoo() {

    Object ourLock = new Object();

    for (;;) {
        Object theirLock = mutex.putIfAbsent(0, ourLock);
        if (theirLock == null) {
            break;
        }

        synchronized (theirLock) {                  // a
            if (mutex.get(0) != theirLock) {        // b
                continue;
            }
            theirLock.wait();                       // c
        }                                           // d
    }

    try {
        // critical section

    } finally {
        synchronized (ourLock) {                    // e
            mutex.remove(0);                        // f
            ourLock.notifyAll();                    // g
        }                                           // h
    }
}

我考虑过 happens-befores :
  • hb(f, h) 和 hb(h, a) 因此 hb(f, a)
  • hb(c, d) 和 hb(d, e) 因此 hb(c, e)

  • 但是,这似乎并不能证明或反驳任何事情。

    编辑:(上述问题未能真正解释这段代码应该做什么。)

    预期的:
  • brokenFoo() 被多个线程调用,上面的代码应该提供对 // critical section 的互斥。
  • 如果两个或多个线程同时进入 brokenFoo() ,则只有一个应该继续进入 // critical section ,而其他线程则在之前的某个地方等待。
  • // critical section 中的线程退出后,另一个应该继续代替它。

  • 实际的:
  • 据观察,即使 c 中没有其他线程,仍有线程在 brokenFoo() 处等待。
  • 最佳答案

    可能是一个线程在 另一个线程开始 wait() 之前调用 notifyAll() 的情况。这可能是由于 spurious wakeup 导致的:

  • 线程 1 进入临界区
  • 线程 2 开始等待 ()
  • 线程 2 中发生虚假唤醒
  • 线程 1 进入同步块(synchronized block)并通知锁上
  • 线程 2 进入同步块(synchronized block),无限等待

  • 或者线程 1 恰好在线程 2 之前执行。虽然您的代码在 JMM 方面是正确的,但不能保证其 liveness 。这就是为什么您应该使用 CountDownLatch 而不是通知/等待机制的原因。

    关于java - 与同步互斥锁的内存不一致,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/24175558/

    10-13 22:03