0

在并发方面我是个新手,在发现问题时我不确定自己,我正在查看一个相当成熟的代码库,发现以下代码(为简洁而编辑),我认为这些代码容易受到数据竞争的影响:

public class Example extends Thread {
    boolean condition = false;

    public void run () {
        while (true) {
            synchronized (this) {
                try {
                    while( condition ) wait();
                }
                catch (InterruptedException e) { /*for brevity*/  }
            }

            // non-blocking computation
        }
    }

    public void setTrue () { condition = true; }

    public void setFalse () {
        synchronized (this) {
            condition = false;
            this.notifyAll();
        }
    }
}

据我了解condition必须是易失性的,因为即使使用同步块,编译器也不会发出任何内存屏障;如果它是编译器中的易失性存储,condition则会setTrue发出StoreEnter.

我是否可以相信上述内容容易受到数据竞争的影响?如果是这样,我如何通过一个例子来见证数据竞赛(而不是简单地知道 JMM 提供的保证)。在循环中随机调用线程的简单测试setTrue不会发现数据竞争。

另外,我认为在这里使用 notifyAll 有点过分,因为要检查一个条件并且只有一个线程会等待它,对吗?

谢谢你。

4

2 回答 2

3

据我了解,条件必须是易失性的,因为即使使用同步块,编译器也不会发出任何内存屏障;如果它是在 setTrue 中设置条件的易失性存储,则编译器将发出 StoreEnter。

这是不正确的。当您在块中使用共享变量时synchronized,您的代码相对于使用相同变量和相同锁的其他线程而言将是线程安全的。如果需要内存屏障,则将使用它们。

但是,您向我们展示的代码是不正确的,因为该方法正在更新块setTrue()之外的标志。synchronized


我是否可以相信上述内容容易受到数据竞争的影响?

是的……有点。场景如下:

  1. 条件是false
  2. 其他一些线程调用setTrue将条件变量设置true在其缓存中。但由于该setTrue方法不使用synchronized,因此没有写屏障,也没有刷新到主内存。
  3. “示例”线程从主内存中获取最新提交的值(仍然是false),并且不会像它应该做的那样等待。

另外,我认为在这里使用 notifyAll 有点过分,因为要检查一个条件,并且只有一个线程会等待它,对吧?

它可以替换为notify()... 如果这就是您的意思。但老实说,您使用哪种类型的通知并没有真正的区别。


你评论说:

我的意思是编译器不会认为在这种情况下有必要提交内存屏障。

也许。但是“monitorenter”和“monitorexit”指令隐含地涉及内存屏障。

和:

如果条件不稳定,那不是也正确吗?

如果您正在谈论使用volatileAND synchronized,那么是的,这将是正确的……尽管这volatile将是多余的(假设该setTrue错误已修复。)

如果你只是在谈论volatile,那么没有。您无法仅使用volatile. 问题是“读取/测试/等待”或“写入/通知”序列都不能原子执行;即没有竞争条件的可能性。

wait/notify此外,如果不使用原始对象互斥锁或对象,您将无法进行等效操作Lock

于 2013-08-18T02:40:11.243 回答
0

我是否可以相信上述内容容易受到数据竞争的影响?

不要这么想。条件不重要,它只允许避免等待的方法。它的设置方式也不重要。它不需要是易失的,因为它的使用是一个对象的本地。

另外,我认为在这里使用 notifyAll 有点过分,因为要检查一个条件并且只有一个线程会等待它,对吗?

NotifyAll 很好,虽然方法中只有一个线程在等待但可能有许多其他线程在等待或等待该线程。

于 2013-08-18T03:03:52.877 回答