怪异的竞争条件?

时间:2016-01-11 23:14:02

标签: java multithreading race-condition

我遇到了我写的方法的问题。做什么,就是等待一个属性等于另一个值。我不能在这里重现它,但在我正在运行的单元测试中,我想等待特定值为真。它打印出“等待”,然后“通知”(表示它应该正常工作),但从未打印“完成等待”。它并不总是这样,有时它完美无瑕,这就是为什么我认为这是一种竞争条件。

之前我使用的是Locking API,但是遇到了同样的问题,所以我决定尝试旧的等待/通知。

在它有机会等待之前,是否可能是正在创建和调用的侦听器?即使这是问题,也不应该打印“等待”,然后“通知”。即使我在“等待”和“通知”时打印ms,等待仍然在通知之前。

private static <T> void waitFor(ObservableValue<T> value, T wantedValue) throws InterruptedException {
    if (value.getValue() != wantedValue) {
        Object lock = new Object();
        synchronized (lock) {
            value.addListener((observableValue, t, t1) -> {
                if (value.getValue() != wantedValue) {
                    return;
                }
                synchronized (lock) {
                    System.out.print("notify");
                    lock.notifyAll();
                }
            });
            System.out.println("wait");
            lock.wait();
            System.out.println("done waiting");
        }
    }
}

2 个答案:

答案 0 :(得分:4)

    Object lock = new Object();
    synchronized (lock) { ...

没有意义。

多个线程必须使用相同的对象进行同步,否则就不会发生同步。

最后,您的代码会一直等待其他线程无法发送到lock对象的信号,因为该对象是该方法的本地对象。

修改:请忽略上述内容。它只是基于我的忽略,锁定对象被传递给匿名监听器,该监听器确实从该方法中获得。

然而,另一个潜在的竞争条件是

if (value.getValue() != wantedValue) {
    Object lock = new Object();
    synchronized (lock) { ...

如果值在 getValue()之后但在执行value.addListener(...)之前异步更改,该怎么办?

另一个小问题:wait()状态documentation和#34;虚假唤醒是可能的,此方法应始终用于循环&#34 ; (强调我的)。

答案 1 :(得分:2)

到目前为止关于你的锁的评论忽略了一个重要的细节。你的锁变量实际上是 NOT 一个局部变量。它在Java 8中是“有效的最终”,因为它在你的监听器闭包中被引用。这意味着它实际上被复制到实现闭包对象的匿名类中的特殊编译器生成字段中,因此,它实际上可能在调用waitFor()的线程和任何线程可能调用已注册的侦听器闭包之间共享。所以,我认为这根本不是问题。

但是,根据您期望发生的情况,您的代码中可能存在竞争条件。在调用wait()之前添加了监听器,这意味着如果在你甚至可以进入wait()之前有一个立即的异步通知,那么监听器对notifyAll()的调用将被遗漏,代码将永远等待已经发生的通知。当然,如果你希望将来有更多的通知,这不是一个真正的问题,因为你会在下一个通知出现时解锁,但我希望你在单元测试中看到这个错误你在哪里等待只有一个通知而你有时会错过它,导致你的测试失败有时候

进一步思考。也许您可以将代码更改为:

ObservableValue.waitFor(T value)

没有static关键字(我希望它不存在),代码几乎总是更好。如果ObservableValue是一个接口,那么你总是可以将waitFor()作为一个默认方法,因为你在Java 8中。