问题 Java - 对可变对象的易失性引用 - 对象的字段的更新对所有线程都是可见的


...没有额外的同步?下面的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;
    }
}
  • 将更新到 numOfKeys 字段对于读者线程是可见的,没有任何明确的同步(注意读者和作者都必须获取一个实例 ReentrantReadWriteLock  - 每个节点的实例相同 - 但除此之外)?如果没有的话 numOfKeys 不稳定吗?
  • 正在改变根本就像 root = new Node() (只有一个编写器线程会更改根,除了调用Tree构造函数的主线程)

有关:

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


10991
2018-05-25 13:16


起源

我不明白的部分 ReentrantReadWriteLock。有没有 ReadWriteLock中?或者问题是否有可能没有 ReadWriteLock中? - nosid
@nosid:编辑了这个问题 - 我提到了锁,因为它存在以协调读者/作者并且它可能会产生影响 - 但问题不在于关于访问可变对象字段的必要同步的锁定 - Mr_and_Mrs_D
您可能希望使内部类为内存效率而静态。 - David Ehrmann
@DavidEhrmann:这是非静态内部类的罕见用法之一(至少在我的设计中) - 节点必须知道树(对于记录它是基于磁盘的b +树) - Mr_and_Mrs_D


答案:


这是两个问题。让我们从第二个开始吧。

将新构造的对象分配给 挥发物 变量很好地工作。每个线程,读取 挥发物 变量,会看到一个 完全构造 目的。没有必要 进一步 同步。这种模式通常与 不可变类型

class Tree {
    private volatile Node node;
    public void update() {
        node = new Node(...);
    }
    public Node get() {
        return node;
    }
}

关于第一个问题。您可以使用 挥发物 用于同步访问的变量 非易失性 变量。以下清单显示了一个示例。想象一下,如图所示初始化两个变量,并且两个方法同时执行。保证,如果第二个线程看到更新 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
    }
}

但是,你的例子是不同的。阅读和写作可能如下所示。与上面的例子相反,两个线程都从中读取 挥发物 变量。然而, 读操作 上 挥发物 变量不会彼此同步。

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;
}

换句话说:如果是线程 一个 写给一个 挥发物 变量 X 和线程  读取写入的值 X,然后在读取操作后,线程  将看到线程的所有写操作 一个,写到之前发生的 X。但没有写入操作 挥发物 变量,对其他变量的更新没有影响。


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

[..]如果所有顺序一致的执行没有数据争用,[..]那么程序的所有执行都将显示为顺序一致。

简单地说,一个 数据竞赛 如果两个线程可以同时访问同一个变量,至少有一个操作是写操作,变量是,则存在 非易失性数据竞赛 可以通过声明来消除 共享变量 如 挥发物。没有 数据竞赛,没有问题 能见度 更新。


4
2018-05-25 15:34



谢谢 - 那会使numOfKeys挥发性足够吗? - Mr_and_Mrs_D
@Mr_and_Mrs_D: volatile 解决了这个问题。我在答案中添加了一个段落,其中包含有关内存模型的最重要规则。 - nosid


没有。

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

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


9
2018-05-25 13:19



嗯读取和写入易失性域确实在关系之前建立了一个发生。那么问题的其他部分呢? - Mr_and_Mrs_D
这意味着标记对象的引用 volatile 只确保每个线程都获得正确的对象,但是当线程尝试读取该对象的属性时,它们仍然可能会获得过时的值,因为该对象上的属性不是 volatile。那是对的吗? - Wang Sheng