Java - 对可变对象的易失性引用 - 对所有线程都可以看到对象字段的更新

Mr_*_*s_D 13 java multithreading volatile java-memory-model

...没有额外的同步?下面的Tree类应该由多个线程访问(它是单例但不通过枚举实现)

class Tree {

    private volatile Node root;

    Tree() {
        root = new Node();
        // the threads are spawned _after_ the tree is constructed
    }

    private final class Node {
        short numOfKeys;
    }
}
Run Code Online (Sandbox Code Playgroud)
  • numOfKeys读取器线程是否可以看到对该字段的更新而没有任何明确的同步(请注意,读者和编写者必须获取ReentrantReadWriteLock每个节点的相同实例 - 但禁止这样做)?如果不能使numOfKeys挥发性足够吗?
  • 将root更改为简单root = new Node()(除了调用Tree构造函数的主线程外,只有编写器线程会更改根)

有关:

编辑:对后5 Java语义感兴趣

SLa*_*aks 10

没有.

volatile字段中放置对象的引用不会以任何方式影响对象本身.

一旦从volatile字段加载对象的引用,就会有一个与任何其他对象没有区别的对象,并且波动率没有进一步的影响.

  • 因此,这意味着标记对象`volatile`的引用只能确保每个线程都获得正确的对象,但是当线程尝试读取该对象的属性时,它们仍然可能会获得过时的值,因为该对象上的属性不是`volatile`.那是对的吗? (10认同)

nos*_*sid 6

有两个问题。让我们从第二个开始。

将新构造的对象分配给volatile变量效果很好。读取volatile变量的每个线程都将看到一个完全构造的对象。无需进一步同步。通常将这种模式与不可变类型结合使用。

class Tree {
    private volatile Node node;
    public void update() {
        node = new Node(...);
    }
    public Node get() {
        return node;
    }
}
Run Code Online (Sandbox Code Playgroud)

关于第一个问题。您可以使用易失性变量来同步对非易失性变量的访问。以下清单显示了一个示例。想象一下,如图所示初始化了两个变量,并且两个方法同时执行。可以保证,如果第二个线程看到到的更新foo,那么它也会看到到的更新bar

volatile int foo = 0;
int bar = 0;

void thread1() {
    bar = 1;
    foo = 1; // write to volatile variable
}

void thread2() {
    if (foo == 1) { // read from volatile variable
        int r = bar; // r == 1
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,您的示例是不同的。阅读和写作可能如下所示。与上面的示例相反,两个线程均从volatile变量读取。但是,对易失性变量的读取操作不会彼此同步。

void thread1() {
    Node temp = root; // read from volatile variable
    temp.numOfKeys = 1;
}

void thread2() {
    Node temp = root; // read from volatile variable
    int r = temp.numOfKeys;
}
Run Code Online (Sandbox Code Playgroud)

换句话说:如果线程A易失性变量x写入并且线程B读取了写入x的值,则在读取操作之后,线程B将看到线程A的所有写入操作,这些操作在写入x之前发生。但是,如果不对易失性变量进行写操作,则不会影响对其他变量的更新。


听起来比实际要复杂。实际上,只有一条要考虑的规则,您可以在JLS8§17.4.5中找到

[..]如果所有顺序一致的执行都没有数据争用,则[..]则该程序的所有执行将看起来是顺序一致的。

简而言之,如果两个线程可以同时访问同一个变量,并且至少一个操作为写操作,并且该变量为非易失性,则存在数据争用。通过将共享变量声明为volatile可以消除数据争用。没有数据竞争,更新的可见性就不会有问题。