为什么以及volatile如何暗示原子读/写?

时间:2014-07-28 10:18:58

标签: java volatile atomicity

首先,我知道volatile不会使多个操作(如i++)原子。这个问题是关于读或写操作。

我最初的理解是volatile只强制执行内存屏障(即其他线程将能够看到更新的值)。

现在我注意到JLS section 17.7说volatile还会产生单个读或写原子。例如,给定两个线程,都将不同的值写入volatile long x,然后x最终将代表其中一个值。

我很好奇这是怎么回事。在32位系统上,如果两个线程并行写入64位并且没有“正确”同步(即某种锁定),则结果应该是混合。为清楚起见,让我们使用一个例子,其中线程1写入0L,而线程2将-1L写入相同的64位存储器位置。

T1 writes lower 32 bit
T2 writes lower 32 bit
T2 writes upper 32 bit
T1 writes upper 32 bit

结果可能是0x0000FFFF,这是不合需要的。 volatile如何阻止这种情况?

我还在别处读过,这通常不会降低性能。如何以极小的速度影响同步写入?

3 个答案:

答案 0 :(得分:4)

volatile 仅强制执行内存屏障(意思是刷新处理器缓存)的声明为false。它还暗示了volatile值的读写组合的发生在关系之前。例如:

class Foo {
  volatile boolean x;
  boolean y;

  void qux() {
    x = true; // volatile write
    y = true;
  }

  void baz() {
    System.out.print(x); // volatile read
    System.out.print(" ");
    System.out.print(y);
  }
}

当您从两个线程运行这两个方法时,上面的代码将打印true falsetrue truefalse false,但不会打false true。如果没有volatile关键字,则无法保证后续条件,因为JIT编译器可能会重新排序语句。

与JIT编译器可以确保这种情况的方式相同,可以保护程序集中的64位值读写。 JIT编译器显式处理volatile值以确保其原子性。某些处理器指令集直接由特定的64位指令支持,否则JIT编译器会对其进行仿真。

JVM比您预期的要复杂得多,并且通常在没有完整范围的情况下进行解释。考虑reading this excellent article,其中包含所有细节。

答案 1 :(得分:0)

volatile确保线程读取的是该点的最新值,但它不会同步两次写入。

如果线程写入正常变量,它会将值保留在线程中,直到发生某些特定事件。如果一个线程写了一个volatile变量,它会立即改变变量的内存。

答案 2 :(得分:0)

  

在32位系统上,如果两个线程并行写入64位并且没有“正确”同步(即某种锁定),则结果可能是混合

如果变量未标记为volatile,确实会发生这种情况。现在,如果字段标记为volatile,系统会怎么做?以下资源解释了这一点:http://gee.cs.oswego.edu/dl/jmm/cookbook.html

  

几乎所有的处理器都支持至少一个粗粒度的屏障指令,通常只称为围栏,它保证在围栏之后发起的所有负载和存储将在围栏之后启动的任何负载或存储之前严格排序[...如果可用,您可以将volatile存储实现为原子指令(例如x86上的XCHG)并省略屏障。如果原子指令比StoreLoad障碍便宜,这可能会更有效

本质上,处理器提供了实现保证的工具,可用的设施取决于处理器。