为了账号安全,请及时绑定邮箱和手机立即绑定

可变变量并刷新到主内存/从主内存读取

可变变量并刷新到主内存/从主内存读取

尚方宝剑之说 2021-06-25 18:01:35
官方说明说,写入易失性字段与监视器释放具有相同的记忆效应,而从易失性字段读取与监视器获取具有相同的记忆效应。和实际上,volatile 的语义得到了大幅加强,几乎达到了同步的水平。出于可见性的目的,对 volatile 字段的每次读取或写入都类似于“一半”同步。这是否意味着,对 volatile 变量的任何写入都会使执行线程将其缓存刷新到主内存中,并且每次从 volatile 字段读取都会使线程从主内存中重新读取其变量?我问是因为完全相同的文本包含此声明重要说明:请注意,两个线程都必须访问相同的 volatile 变量,以便正确设置happens-before 关系。线程 A 在写入 volatile 字段 f 时可见的所有内容在线程 B 读取 volatile 字段 g 后都可见,但情况并非如此。释放和获取必须“匹配”(即在同一个易失性字段上执行)以具有正确的语义。而这个说法让我很困惑。我确信使用同步语句获取和释放常规锁不是真的 - 如果某个线程释放任何监视器,那么它所做的所有更改对所有其他线程都是可见的(更新:实际上不是真的 - 观看最佳答案)。在stackoverflow上甚至有一个关于它的问题。然而,据说无论出于何种原因,对于 volatile 字段都不是这种情况。我无法想象任何发生在保证之前的实现,它不会使其他线程、不读取相同 volatile 变量的线程可见更改。至少想象一个实现,它与前两个引号并不矛盾。此外,在发布这个问题之前,我做了一些研究,例如这篇文章,其中包含这句话执行这些指令后,所有写入都可以通过缓存子系统或主内存对所有其他线程可见。提到的指令是在写入 volatile 字段时发生的指令。那么这个重要的笔记应该是什么意思呢?或者我错过了什么?或者,也许那张纸条是完全错误的?回答?经过更多的研究,我只能在关于 volatile 字段及其对非 volatile 字段变化的影响的官方文档中找到这样的说法:使用 volatile 变量可以降低内存一致性错误的风险,因为对 volatile 变量的任何写入都会与对该相同变量的后续读取建立先发生关系。这意味着对 volatile 变量的更改始终对其他线程可见。更重要的是,这也意味着当一个线程读取一个 volatile 变量时,它不仅会看到对 volatile 的最新更改,还会看到导致更改的代码的副作用。我不知道这是否足以得出结论,只有读取相同 volatile 的线程才能保证发生之前的关系。所以现在我只能总结结果是不确定的。但在实践中,我建议考虑 thread 所做的更改A,当它写入 volatile 字段时,B只有当线程B读取相同的 volatile 字段时,才保证对线程可见。以上来自官方来源的引用强烈暗示了这一点。
查看完整描述

2 回答

?
aluckdog

TA贡献1847条经验 获得超7个赞

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

你错的根本地方是:

我肯定知道常规锁获取不是真的......

在实践中x86,您可能是对的,但JLS 官方 oracle 教程要求

当一个线程释放一个内在锁时,在该操作和相同锁的任何后续获取之间建立了一个happens-before 关系。

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

同样的事情发生在一个volatile- 一些线程写入它,并且当另一个线程通过后续读取观察到写入时,建立了happens-before。


查看完整回答
反对 回复 2021-06-30
?
千万里不及你

TA贡献1784条经验 获得超9个赞

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


不,不是那个意思。那样想是一个常见的错误。它的全部含义是 Java 内存模型中指定的内容。


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


为了提供一个例子,让我们看看 volatile 变量是如何实现的(对于这个例子)


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     $0x18,%rsp

  0x00007f87d1085867: mov     %rbp,0x10(%rsp)   ;*synchronization entry

                                                ; - com.test.Volatlee::inc@-1 (line 26)


  0x00007f87d108586c: movabs  $0x7191fab68,%r10  ;   {oop(a 'java/lang/Class' = 'com/test/Volatlee')}

  0x00007f87d1085876: mov     0x68(%r10),%r11

  0x00007f87d108587a: add     $0x1,%r11

  0x00007f87d108587e: mov     %r11,0x68(%r10)

  0x00007f87d1085882: lock addl $0x0,(%rsp)     ;*putstatic a

                                                ; - com.test.Volatlee::inc@5 (line 26)


  0x00007f87d1085887: add     $0x10,%rsp

  0x00007f87d108588b: pop     %rbp

  0x00007f87d108588c: test    %eax,0xca8376e(%rip)  ;   {poll_return}

  0x00007f87d1085892: retq

  ;tons of hlt ommited

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


查看完整回答
反对 回复 2021-06-30
  • 2 回答
  • 0 关注
  • 184 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信