Java内存模型对于volatile的保证

4
我对volatile语义有一些疑问。
假设有三个线程T1、T2和T3以及给定类的一个实例。
class Foo {
    private int x = 1;
    private int y = 2;
    private int z = 3;
    private volatile int w = 4;
    private volatile int v = 5;

    public void setX(int x) {
        this.x = x;
    }

    public int getX() {
        return this.x;
    }

    (...)
}

假设发生了以下读/写操作序列:

1. (T1) foo.getX(); // stored in a local memory of T1
2. (T1) foo.getY(); // stored in a local memory of T1

3. (T2) foo.setX(10);
4. (T2) foo.setY(20);

5. (T3) foo.getY(); // T3 may see 2 or 20, no guarantees if a write action from point 4 is visible to T3
6. (T3) foo.setZ(30);
7. (T3) foo.setW(40);
8. (T3) foo.setV(50);

9. (T1) foo.getW()
10. (T1) foo.getZ()
11. (T1) foo.getY()
12. (T1) foo.getX()

我知道在第9个点,T1将看到在第7个点设置的值,并且在第10个点,T1将看到在第6个点设置的值(确切地说,至少与此值一样最新)。

但是,这些陈述是否正确?

  1. Java内存模型保证T1在第11点至少能看到一个与T3在第5点看到的值同步的值(来自于T3的本地内存或者更加实际的值,但即使共享内存中有一个更加实际的值,它可能对T1不可见)。
  2. 在第12点,T1所看到的值没有任何保证,特别是没有保证它能看到在第3点设置的值。此外,如果在线程中的任何一个点之前对x进行了任何写操作,T1在第12点可能会看到一些旧的值。如果在第7点时,T3的本地内存中存在任何x的值,则JMM保证T1在第12点将看到该值,但是假设在第7点之前的T3中没有对x进行任何写/读操作,则没有这样的保证。
  3. 第8点和第9点之间不存在happens-before关系,因为它们是不同的volatile变量。如果JVM以一种方式实现Java内存模型,在读取volatile变量时将本地内存刷新到共享内存,并在写入volatile变量时使本地内存无效,就像article中描述的synchronized语义一样,那么副作用将是第8点和第9点之间存在happens-before关系,但它在Java语言规范中没有严格定义。
  4. 如果在第7点和第8点有读操作而不是写操作,T1在第10点仍将看到值30,因为happens-before关系不仅适用于写-读volatile序列,还适用于读-读、写-写和读-写volatile序列。
请确认我的理解是正确的。

你没有展示给我们getter和setter的定义。你是真的想要直接访问/更新变量吗?请注意,JMM仅规定用于读写共享变量,而不是方法调用。 - Stephen C
是的,这只是一个简单的POJO。 - Bartosz Popiela
1个回答

1
只要您的get/set操作仅获取和设置变量,那么您的所有假设都将是正确的。
在Java中,变量存储在内存中。但编译器(和运行时)会允许将变量暂时存储在CPU缓存中,以便在算法或代码段的持续时间内更快地读取和写入。
这种缓存的缺点在于,当Core完成变量时,它将将其写回内存,就像只更新了一次一样。其他核心无法看到变量在使用时的状态。更糟糕的是,没有关于何时将其写回内存的顺序保证。
通过将变量设置为Volatile,您告诉Java不允许将变量放入任何缓存中。必须在内存中进行对变量的读取或写入。
这意味着Volatile将使变量的单个操作原子化。但它也会使变量的长时间操作变得非常慢。因此,volatile不是从多线程代码中获得性能增益的解决方案。
值得注意的是,需要进行多次读取或写入的操作是原子操作。例如i ++,实际上是i = i + 1,在写入完成之前i的值可能已经改变了。
如果您需要确保操作具有原子性,可以使用锁或 semtex(较慢),或者您可以使用聪明的编程范例,例如Copy-On-Write(COW)来实现原子读写。

变量不允许放入任何缓存 - 我认为这个说法并不一定正确。请参考答案 - Bartosz Popiela
Oracle文档清楚地说明了如何处理易失性变量。我理解你的观点,但我认为易失性变量在写入后会立即刷新。除非你指的是与Oracle实现不同的iced tea运行时。 - Parthanon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接