易失性变量并从主内存中刷新 to/reads

Volatile variable and flushes to/reads from main memory

官方说明说,

Writing to a volatile field has the same memory effect as a monitor release, and reading from a volatile field has the same memory effect as a monitor acquire.

Effectively, the semantics of volatile have been strengthened substantially, almost to the level of synchronization. Each read or write of a volatile field acts like "half" a synchronization, for purposes of visibility.

来自 here.

这是否意味着,对 volatile 变量的任何写入都会使执行线程将其缓存刷新到主内存中,并且每次从 volatile 字段读取都会使线程从主内存中重新读取其变量?

我问是因为同一文本包含此语句

Important Note: Note that it is important for both threads to access the same volatile variable in order to properly set up the happens-before relationship. It is not the case that everything visible to thread A when it writes volatile field f becomes visible to thread B after it reads volatile field g. The release and acquire have to "match" (i.e., be performed on the same volatile field) to have the right semantics.

而且这个说法让我很疑惑。我确信对于同步语句的常规锁获取和释放是不正确的 - 如果某个线程释放任何监视器,那么它所做的所有更改对所有其他线程都是可见的(更新:实际上不是这样 - 观看最佳答案)。 Whosebug 上什至还有一个 question about it。然而,据说无论出于何种原因,易失性字段都不是这种情况。我无法想象 happens-before 保证的任何实现,它不会使更改对其他线程不可见,线程不读取相同的 volatile 变量。至少想象一个与前两个引号不矛盾的实现。

而且在发这个问题之前我做了一些研究,例如this article,其中包含这句话

After executing these instructions, all writes are visible to all other threads through cache subsystem or main memory.

提到的指令是在写入易失性字段时发生的指令。

那条重要提示是什么意思?或者我错过了什么?或许那张纸条完全错了?

回答?

经过更多研究,我只能在官方文档中找到关于易失性字段及其对非易失性字段变化的影响的声明:

Using volatile variables reduces the risk of memory consistency errors, because any write to a volatile variable establishes a happens-before relationship with subsequent reads of that same variable. This means that changes to a volatile variable are always visible to other threads. What's more, it also means that when a thread reads a volatile variable, it sees not just the latest change to the volatile, but also the side effects of the code that led up the change.

来自 here.

我不知道这是否足以得出结论,只有线程读取相同的 volatile 时才会保证 happens-before 关系。所以目前我只能总结一下,结果没有定论。

但在实践中,我建议考虑线程 A 所做的更改,当它写入易失性字段时,只有在线程 B 时,才能保证对线程 B 可见] 读取相同的可变字段。以上引自官方消息强烈暗示。

Does that mean, that any write to a volatile variable makes executing thread flush its cache into main memory and every read from a volatile field makes the thread reread its variables from main memory?

不,不是那个意思。以这种方式思考是一个常见的错误。它的意思就是 Java 内存模型中指定的内容。

在英特尔 CPU 上有刷新缓存行的指令:clflush and clflushopt 并且在发生易失性写入时对整个缓存行进行这种刷新是非常低效的。

为了提供示例,让我们看一下

如何实现易失性变量(对于此示例)
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)

为了我的哈斯韦尔。让我们写这个简单的例子:

public static volatile long a = 0;

public static void main(String[] args){
    Thread t1 = new Thread(() -> {
        while(true){
            //to avoid DCE
            if(String.valueOf(String.valueOf(a).hashCode()).equals(String.valueOf(System.nanoTime()))){
                System.out.print(a);
            }
        }
    });

    Thread t2 = new Thread(() -> {
        while(true){
            inc();
        }
    });

    t1.start();
    t2.start();
}

public static void inc(){
    a++;
}

我禁用了分层编译并 运行 它与 C2 编译器如下:

java -server -XX:-TieredCompilation -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*Volatile.inc -jar target/test-0.0.1.jar

输出如下:

  # {method} {0x00007f87d87c6620} 'inc' '()V' in 'com/test/Volatlee'
  #           [sp+0x20]  (sp of caller)
  0x00007f87d1085860: sub     [=13=]x18,%rsp
  0x00007f87d1085867: mov     %rbp,0x10(%rsp)   ;*synchronization entry
                                                ; - com.test.Volatlee::inc@-1 (line 26)

  0x00007f87d108586c: movabs  [=13=]x7191fab68,%r10  ;   {oop(a 'java/lang/Class' = 'com/test/Volatlee')}
  0x00007f87d1085876: mov     0x68(%r10),%r11
  0x00007f87d108587a: add     [=13=]x1,%r11
  0x00007f87d108587e: mov     %r11,0x68(%r10)
  0x00007f87d1085882: lock addl [=13=]x0,(%rsp)     ;*putstatic a
                                                ; - com.test.Volatlee::inc@5 (line 26)

  0x00007f87d1085887: add     [=13=]x10,%rsp
  0x00007f87d108588b: pop     %rbp
  0x00007f87d108588c: test    %eax,0xca8376e(%rip)  ;   {poll_return}
  0x00007f87d1085892: retq
  ;tons of hlt ommited

因此在这个简单的示例中,volatile 编译为 locked 指令,要求缓存行具有要执行的 exclusive 状态(可能向其他内核发送读取无效信号,如果它不是)。

你从一个完全错误的角度来看这个问题。首先,您引用 JLS 而不是谈论 flush,这将是该规范的实现细节。您绝对唯一需要依赖的东西 是 JLS,其他任何东西都可以知道,但不能证明任何形式或形式的规范是对还是错。

而你根本错误的地方是:

I know for sure that it's not true for regular lock acquire...

实际上,在 x86 上,您可能是对的,但是 JLS and the official oracle tutorial mandates that:

When a thread releases an intrinsic lock, a happens-before relationship is established between that action and any subsequent acquisition of the same lock.

Happens-before 是为 后续 动作建立的(如果你愿意,阅读 two 动作,如果它对你来说更简单的话)。一个线程释放锁,另一个线程获取它 - 这些是后续的 (release-acquire semantics).

同样的事情发生在 volatile - 一些线程写入它,并且 当一些其他线程通过 后续 [= 观察到写入 34=] 阅读,happens-before成立。