简体   繁体   English

Java中的易失性变量

[英]Volatile variable in Java

So I am reading this book titled Java Concurrency in Practice and I am stuck on this one explanation which I cannot seem to comprehend without an example. 所以我正在阅读这本名为Java Concurrency in Practice的书,我坚持这个解释,如果没有一个例子,我似乎无法理解。 This is the quote: 这是引用:

When thread A writes to a volatile variable and subsequently thread B reads that same variable, the values of all variables that were visible to A prior to writing to the volatile variable become visible to B after reading the volatile variable. 当线程A写入易失性变量并且随后线程B读取相同的变量时,在写入易失性变量之前, A可见的所有变量的值在读取volatile变量后变为B可见。

Can someone give me a counterexample of why "the values of ALL variables that were visible to A prior to writing to the volatile variable become visible to B AFTER reading the volatile variable"? 有人可以给我一个反例,说明为什么“在写入易变量之前A可见的所有变量的值在读取易变量后变为B可见”?

I am confused why all other non-volatile variables do not become visible to B before reading the volatile variable? 我很困惑为什么在读取volatile变量之前所有其他非易失性变量都不会被B看到?

Declaring a volatile Java variable means: 声明一个易变的Java变量意味着:

  • The value of this variable will never be cached thread-locally: all reads and writes will go straight to "main memory". 该变量的值永远不会被线程本地缓存:所有读取和写入将直接进入“主存储器”。
  • Access to the variable acts as though it is enclosed in a synchronized block, synchronized on itself. 对变量的访问就像包含在同步块中一样,自身同步。

Just for your reference, When is volatile needed ? 仅供参考,何时需要挥发性?

When multiple threads using the same variable, each thread will have its own copy of the local cache for that variable. 当多个线程使用相同的变量时,每个线程将拥有自己的该变量的本地缓存副本。 So, when it's updating the value, it is actually updated in the local cache not in the main variable memory. 因此,当它更新值时,它实际上在本地缓存中更新,而不是在主变量内存中更新。 The other thread which is using the same variable doesn't know anything about the values changed by the another thread. 使用相同变量的另一个线程对另一个线程更改的值一无所知。 To avoid this problem, if you declare a variable as volatile, then it will not be stored in the local cache. 要避免此问题,如果将变量声明为volatile,则它不会存储在本地缓存中。 Whenever thread are updating the values, it is updated to the main memory. 每当线程更新值时,它都会更新到主存储器。 So, other threads can access the updated value. 因此,其他线程可以访问更新的值。

From JLS §17.4.7 Well-Formed Executions 来自JLS§17.4.7 良好的执行

We only consider well-formed executions. 我们只考虑结构良好的执行。 An execution E = < P, A, po, so, W, V, sw, hb > is well formed if the following conditions are true: 如果满足以下条件,则执行E = <P,A,po,so,W,V,sw,hb>形成良好:

  1. Each read sees a write to the same variable in the execution. 每次读取都会在执行中看到对同一变量的写入。 All reads and writes of volatile variables are volatile actions. 易失性变量的所有读写都是易失性动作。 For all reads r in A, we have W(r) in A and W(r).v = rv The variable rv is volatile if and only if r is a volatile read, and the variable wv is volatile if and only if w is a volatile write. 对于A中的所有读取r,我们在A和W(r)中有W(r).v = rv当且仅当r是易失性读取时,变量rv是易失性的,并且当且仅当w时,变量wv是易失性的是一个易变的写。

  2. Happens-before order is a partial order. 发生在订单之前是部分订单。 Happens-before order is given by the transitive closure of synchronizes-with edges and program order. 发生前 - 顺序是通过与边缘和程序顺序同步的传递闭包给出的。 It must be a valid partial order: reflexive, transitive and antisymmetric. 它必须是有效的偏序:反身,传递和反对称。

  3. The execution obeys intra-thread consistency. 执行遵循线程内一致性。 For each thread t, the actions performed by t in A are the same as would be generated by that thread in program-order in isolation, with each write wwriting the value V(w), given that each read r sees the value V(W(r)). 对于每个线程t,由A中的t执行的动作与该线程在隔离的程序顺序中生成的动作相同,每个写入写入值V(w),假设每个读取r看到值V( W(R))。 Values seen by each read are determined by the memory model. 每次读取所见的值由存储器模型确定。 The program order given must reflect the program order in which the actions would be performed according to the intra-thread semantics of P. 给出的程序顺序必须反映根据P的线程内语义执行动作的程序顺序。

  4. The execution is happens-before consistent (§17.4.6). 执行是在一致之前发生的(§17.4.6)。

  5. The execution obeys synchronization-order consistency. 执行遵循同步顺序一致性。 For all volatile reads r in A, it is not the case that either so(r, W(r)) or that there exists a write win A such that wv = rv and so(W(r), w) and so(w, r). 对于A中的所有易失性读取r,情况并非如此(r,W(r))或存在写入获胜A,使得wv = rv等等(W(r),w)等等( w,r)。

Useful Link : What do we really know about non-blocking concurrency in Java? 有用的链接: 我们真正了解Java中的非阻塞并发性?

Thread B may have a CPU-local cache of those variables. 线程B可能具有这些变量的CPU本地缓存。 A read of a volatile variable ensures that any intermediate cache flush from a previous write to the volatile is observed. 读取volatile变量可确保观察到从先前写入volatile的任何中间缓存刷新。

For an example, read the following link, which concludes with "Fixing Double-Checked Locking using Volatile": 有关示例,请阅读以下链接,其中以“使用易失性修复双重检查锁定”结束:

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

If a variable is non-volatile, then the compiler and the CPU, may re-order instructions freely as they see fit, in order to optimize for performance. 如果变量是非易失性的,则编译器和CPU可以根据需要自由地重新排序指令,以便优化性能。

If the variable is now declared volatile, then the compiler no longer attempts to optimize accesses (reads and writes) to that variable. 如果变量现在声明为volatile,则编译器不再尝试优化对该变量的访问(读取和写入)。 It may however continue to optimize access for other variables. 但是,可以继续优化其他变量的访问。

At runtime, when a volatile variable is accessed, the JVM generates appropriate memory barrier instructions to the CPU. 在运行时,当访问volatile变量时,JVM会为CPU生成适当的内存屏障指令。 The memory barrier serves the same purpose - the CPU is also prevent from re-ordering instructions. 内存屏障具有相同的用途 - CPU也可以防止重新排序指令。

When a volatile variable is written to (by thread A), all writes to any other variable are completed (or will atleast appear to be) and made visible to A before the write to the volatile variable; 当一个volatile变量写入(通过线程A)时,所有对任何其他变量的写操作都会完成(或者至少会显示),并且在写入volatile变量之前对A可见; this is often due to a memory-write barrier instruction. 这通常是由于内存写入屏障指令。 Likewise, any reads on other variables, will be completed (or will appear to be) before the read (by thread B); 同样,对其他变量的任何读取都将在读取之前完成(或将显示为)(通过线程B); this is often due to a memory-read barrier instruction. 这通常是由于内存读屏障指令。 This ordering of instructions that is enforced by the barrier(s), will mean that all writes visible to A, will be visible B. This however, does not mean that any re-ordering of instructions has not happened (the compiler may have performed re-ordering for other instructions); 这种由屏障强制执行的指令排序将意味着对A可见的所有写入都将是可见的B.然而,这并不意味着没有发生任何指令的重新排序(编译器可能已执行)重新订购其他指示); it simply means that if any writes visible to A have occurred, it would be visible to B. In simpler terms, it means that strict-program order is not maintained. 它只是意味着如果A发现任何可见的写入,它就会对B可见。简单来说,这意味着不保持严格的程序顺序。

I will point to this writeup on Memory Barriers and JVM Concurrency , if you want to understand how the JVM issues memory barrier instructions, in finer detail. 如果您想更详细地了解JVM如何发布内存屏障指令,我将在内存屏障和JVM并发上指出这篇文章。

Related questions 相关问题

  1. What is a memory fence? 什么是记忆围栏?
  2. What are some tricks that a processor does to optimize code? 处理器优化代码的一些技巧是什么?

Threads are allowed to cache variable values that other threads may have since updated since they read them. 允许线程缓存其他线程自更新以来可能具有的变量值。 The volatile keyword forces all threads to not cache values. volatile关键字强制所有线程不缓存值。

This is simply an additional bonus the memory model gives you, if you work with volatile variables. 如果你使用volatile变量,这只是内存模型给你的额外奖励。

Normally (ie in the absence of volatile variables and synchronization), the VM can make variables from one thread visible to other threads in any order it wants, or not at all. 通常(即,在没有volatile变量和同步的情况下),VM可以按照它想要的任何顺序将变量从一个线程可见到其他线程,或者根本不存在。 Eg the reading thread could read some mixture of earlier versions of another threads variable assignments. 例如,阅读线程可以读取另一个线程变量赋值的早期版本的混合。 This is caused by the threads being maybe run on different CPUs with their own caches, which are only sometimes copied to the "main memory", and additionally by code reordering for optimization purposes. 这是因为线程可能在具有自己的高速缓存的不同CPU上运行,这些高速缓存有时仅被复制到“主存储器”,并且还通过代码重新排序以用于优化目的。

If you used a volatile variable, as soon as thread B read some value X from it, the VM makes sure that anything which thread A has written before it wrote X is also visible to B. (And also everything which A got guaranteed as visible, transitively). 如果你使用了一个volatile变量,一旦线程B从中读取了一些值X,VM就会确保线程A在写入X之前写入的任何内容对于B也是可见的。(以及A保证为可见的所有内容) ,传递)。

Similar guarantees are given for synchronized blocks and other types of locks. 对同步块和其他类型的锁给出了类似的保证。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM