是否可以通过使用单个volatile来防止无序执行

时间:2011-01-31 15:43:00

标签: java multithreading

通过引用文章,它使用一对volatile来防止无序执行。我想知道,是否可以使用单volatile来阻止它?

void fun_by_thread_1() {
    this.isNuclearFactory = true;
    this.factory = new NuclearFactory();
}

void fun_by_thread_2() {
    Factory _factory = this.factory;
    if (this.isNuclearFactory) {
        // Do not operate nuclear factory!!!
        return;
    }
    // If out-of-order execution happens, _factory might 
    // be NuclearFactory instance.
    _factory.operate();
}

Factory factory = new FoodFactory();
volatile boolean isNuclearFactory = false;

我问的原因是因为我有一个单一的保护标志(类似于isNuclearFactory标志),以防止多个变量(类似于许多Factory)。我不希望将所有Factory标记为易失性。

或者,我应该落入以下解决方案吗?

void fun_by_thread_1() {
    writer.lock();
    try {
        this.isNuclearFactory = true;
        this.factory = new NuclearFactory();
    } finally {
        writer.unlock();
    }
}

void fun_by_thread_2() {
    reader.lock();
    try {
        Factory _factory = this.factory;
        if (this.isNuclearFactory) {
            // Do not operate nuclear factory!!!
            return;
        }
    } finally {
        reader.unlock();
    }
    _factory.operate();
}

Factory factory = new FoodFactory();
boolean isNuclearFactory = false;

P / S:我知道instanceof。工厂只是一个证明无序问题的例子。

4 个答案:

答案 0 :(得分:4)

您的第一个解决方案存在的问题是,如果factory不易变,则没有可见性保证。也就是说,如果在一个线程中更改了它,则其他线程可能无法在看到更改的isNuclearFactory值的同时看到更改的值,或者甚至可能根本看不到它。

所以有可能

  1. 主题A调用fun_by_thread_1()
  2. 主题B调用fun_by_thread_2(),它会看到isNuclearFactory == true,但仍会看到factory的前一个值,可能是null,或者可能是非fun_by_thread_1()核工厂。
  3. 幸运的是,对这一特定问题的修复很容易:改变void fun_by_thread_1() { this.factory = new NuclearFactory(); this.isNuclearFactory = true; } 中作业的顺序。

    fun_by_thread_2()

    更改volatile值会传播到所有其他线程;此外,它还保证了在触摸volatile变量之前当前线程所做的所有更改的可见性。

    然而,这引入了一个新的数据竞争问题:现在可能

    1. 线程B调用factory并获取对fun_by_thread_1()的引用,这恰好指向非核工厂
    2. 主题A调用isNuclearFactory
    3. 线程B测试true并看到它是_factory(由线程A设置),所以它返回时不使用fun_by_thread_1(),尽管它指的是非核工厂!
    4. 也有可能

      1. 线程A调用fun_by_thread_2()并创建一个新的核工厂
      2. 线程B调用factory并获得isNuclearFactory的引用,这恰好指向新的核工厂
      3. 线程B测试false并发现它仍然是_factory,因此它运行核volatile
      4. 所以我对你的标题问题的回答是。实际上,即使有两个volatile变量,您也很容易出现问题。每当你有两个不同但逻辑相关的变量暴露给多个线程时,最好的解决方案是将它们封装到一个对象中,这样你就可以通过改变单个({{1}})引用来一次更新这两个值。 / p>

答案 1 :(得分:1)

我认为你的代码不会阻止使用单个volatile乱序执行编译器。它与多种挥发物有关,因为挥发物不能在彼此之间重新排序,但在某些情况下可以再次进行挥发性和正常的装载/储存,这是其中之一。

基于JSR 133 cookbook,后面的操作序列可能导致编译器重新排序:

 1st - Volatile-store/monitor-exit
 2nd - Normal-load/normal-store

此序列可以允许编译器重新排序,因此该方法可能看起来像

void fun_by_thread_1() {
    this.factory = new NuclearFactory();
    this.isNuclearFactory = true;
}

这会有意想不到的结果,因为你可以想象如果fun_by_thread_1在this.factory之后停止,并且fun_by_thread_2在停止之后开始。

编辑:

回答你的其他问题。如果你想引入一个ReadWriteLock,你最好只使用两个挥发性物质。大多数处理器(例如x86)上的易失性读取成本较低,然后获取读或写锁。

如果仅仅是为了知识读者/作者锁模型为什么会起作用的观点。不允许编译器在任何锁定之外提升读取或写入(无论是内部锁定还是j.u.c.Lock)。然而,它可以降低同步块内的读取或写入(称为锁定粗化)。

话虽如此,ReadWriteLock内部的读写兼容程序具有多个线程的连续一致性。这告诉我们的是,即使编译器将重新排序this.factorythis.isNuclearFactory的写入(在锁定方法中),其他线程也会看到它们就像它们在原始文件中一样(就像串行一样) )订单。

简而言之,它可能会阻止j.u.c.Lock方法之外的编译器重新排序,但在锁定方法中完成的任何重新排序都不会对整个程序流产生负面影响。

答案 2 :(得分:1)

我得到了一篇非常可靠的参考文章

http://www.ibm.com/developerworks/library/j-jtp03304/

  

在新的内存模型下,何时   线程A写入volatile变量   V,和线程B从V读取,任何   A可见的变量值   在V写的时候   保证现在可以看到B

要线程1,在易变isNuclearFactory设置为true之前,factory将始终为FoodFactory。上面的语句也应该应用于线程2,因为在isNuclearFactory被写入时对线程1可见的任何变量值现在都保证对B可见。

答案 3 :(得分:0)

无论属性可见性问题如何,我认为如果没有锁定机制,您无法完成此工作。在您的简单示例中,即使解决了无序问题,在将isNuclearFactory设置为true后也可以抢占第1个线程。在这种情况下,线程2将错过食品工厂的执行。