【问题标题】:Non volatile status flag in multi-threaded code多线程代码中的非易失性状态标志
【发布时间】:2013-08-18 02:20:20
【问题描述】:

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

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 必须是易失性的,因为即使使用同步块,编译器也不会发出任何内存屏障;如果它是在setTrue 中的condition 的易失性存储,编译器将发出StoreEnter.

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

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

谢谢。

【问题讨论】:

  • 除非你真的有理由,否则不要扩展 Thread 类。最好实现 Runnable 接口并将类的实例提供给 Thread 构造函数。

标签: java concurrency volatile java.util.concurrent


【解决方案1】:

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

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

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


我是否认为上述内容容易受到数据竞争的影响?

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

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

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

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


你评论了:

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

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

还有:

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

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

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

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

【讨论】:

  • 我的意思是编译器不会认为在这种情况下有必要提交内存屏障。如果条件不稳定,那不是也正确吗?
【解决方案2】:

我是否认为上述内容容易受到数据竞争的影响?

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

另外,我认为 notifyAll 的使用在这里是多余的,因为有 一个要检查的条件,只有一个线程会等待它, 对吧?

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

【讨论】:

    猜你喜欢
    • 2019-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多