Java中的易失性变量

发布于 2024-11-13 23:55:20 字数 399 浏览 2 评论 0原文

因此,我正在阅读这本名为Java Concurrency in Practice的书,并且我被这个解释所困扰,如果没有示例,我似乎无法理解它。这是引用:

当线程A写入易失性 变量和随后的线程 B 读取同一个变量,值 所有可见的变量 A 在写入易失性之前 之后变量对 B 可见 读取易失性变量。

有人可以给我一个反例,解释为什么“在写入易失性变量之前对A可见的所有变量的值在读取易失性变量之后对B变得可见” ?

我很困惑为什么在读取易失性变量之前所有其他非易失性变量对 B 不可见?

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. 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.

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"?

I am confused why all other non-volatile variables do not become visible to B before reading the volatile variable?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

泡沫很甜 2024-11-20 23:55:20

声明一个易失性 Java 变量意味着:

  • 该变量的值永远不会在线程本地缓存:所有读取和写入都将直接进入“主内存”。
  • 对变量的访问就像将其包含在同步块中一样,并对其自身进行同步。

仅供参考,什么时候需要 volatility ?

当多个线程使用同一个
变量,每个线程都会有它的
自己的本地缓存副本
多变的。所以,当它更新时
值,它实际上是在
本地缓存不在主变量中
记忆。另一个线程是
使用相同的变量不知道
有关值的任何更改
另一个线程。为了避免这种情况
问题,如果你将一个变量声明为
易失性,则不会被存储
在本地缓存中。每当线程
正在更新值,已更新
到主存储器。所以,其他线程
可以访问更新后的值。

来自 JLS §17.4.7 < em>格式良好的执行

我们只考虑格式良好的
处决。执行 E = < P、A、
po、so、W、V、sw、hb>结构良好
如果满足以下条件:

  1. 每次读取都会看到相同的写入
    执行中的变量。全部阅读
    易失性变量的写入是
    不稳定的行动。对于所有读入 r
    A,我们在 A 中有 W(r),并且 W(r).v = rv
    变量 rv 是易失性的,如果并且
    仅当 r 是易失性读取且
    变量 wv 是易失性的当且仅
    如果 w 是易失性写入。

  2. 发生在订单之前是部分订单
    命令。发生在订单发出之前
    通过传递闭包
    与边缘和程序同步
    命令。它必须是有效的部分
    顺序:自反、及物
    反对称。

  3. 执行服从
    线程内一致性。对于每个
    线程 t,t 执行的操作
    A 中的内容与
    由该线程生成
    程序顺序是孤立的,每个
    写出值 V(w),给定
    每次读取 r 都会看到该值
    V(W(r))。每次读取看到的值是
    由内存模型决定。这
    给出的程序顺序必须反映
    程序中的动作顺序
    将根据
    P 的线程内语义。

  4. 执行是发生前一致的
    (第 17.4.6 节)。

  5. 执行服从
    同步顺序一致性。为了
    所有易失性读取 A 中的 r,它不是
    so(r, W(r)) 或
    存在一个写赢 A 这样
    wv = rv 且 so(W(r), w) 且
    所以(w,r)。

有用的链接:做什么我们真的了解 Java 中的非阻塞并发吗?

Declaring a volatile Java variable means:

  • 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. 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

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:

  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 = r.v.
    The variable r.v is volatile if and
    only if r is a volatile read, and the
    variable w.v is volatile if and only
    if w is a volatile write.

  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)). 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.

  4. The execution is happens-before consistent
    (§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 w.v = r.v and so(W(r), w) and
    so(w, r).

Useful Link : What do we really know about non-blocking concurrency in Java?

江湖正好 2024-11-20 23:55:20

线程 B 可能具有这些变量的 CPU 本地缓存。读取易失性变量可确保观察到先前写入易失性变量的任何中间缓存刷新。

有关示例,请阅读以下链接,其结尾为“使用 Volatile 修复双重检查锁定”:

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

Thread B may have a CPU-local cache of those variables. A read of a volatile variable ensures that any intermediate cache flush from a previous write to the volatile is observed.

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

美男兮 2024-11-20 23:55:20

如果变量是非易失性的,则编译器和 CPU 可以根据需要自由地重新排序指令,以优化性能。

如果该变量现在被声明为易失性,则编译器不再尝试优化对该变量的访问(读取和写入)。然而,它可能会继续优化其他变量的访问。

在运行时,当访问易失性变量时,JVM 会向 CPU 生成适当的内存屏障指令。内存屏障具有相同的目的——CPU 也被阻止重新排序指令。

当(由线程 A)写入 易失性变量时,对任何其他变量的所有写入都已完成(或至少看起来),并且在写入易失性变量之前对 A 可见;这通常是由于内存写入屏障指令造成的。同样,对其他变量的任何读取都将在
读取(由线程 B);这通常是由于内存读取屏障指令造成的。由屏障强制执行的指令排序意味着所有对 A 可见的写入对 B 都可见。但是,这并不意味着指令的任何重新排序尚未发生了(编译器可能对其他指令执行了重新排序);它只是意味着如果发生了对 A 可见的任何写入,那么它对 B 也是可见的。简单来说,这意味着不维护严格的程序顺序。

如果您想了解如何JVM 发出更详细的内存屏障指令。

相关问题

  1. 什么是内存栅栏?
  2. 处理器优化代码有哪些技巧?< /a>

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.

If the variable is now declared volatile, then the compiler no longer attempts to optimize accesses (reads and writes) to that variable. 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. The memory barrier serves the same purpose - the CPU is also prevent from re-ordering instructions.

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; 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); 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); 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.

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.

Related questions

  1. What is a memory fence?
  2. What are some tricks that a processor does to optimize code?
清君侧 2024-11-20 23:55:20

允许线程缓存其他线程在读取后​​可能已更新的变量值。 volatile 关键字强制所有线程不缓存值。

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.

掩耳倾听 2024-11-20 23:55:20

如果您使用易失性变量,这只是内存模型给您带来的额外好处。

通常(即在没有易失性变量和同步的情况下),VM 可以使一个线程中的变量按其想要的任何顺序对其他线程可见,或者根本不可见。例如,读取线程可以读取另一个线程变量分配的早期版本的某种混合。这是由于线程可能在具有自己的缓存的不同 CPU 上运行,这些线程仅有时被复制到“主内存”,此外还通过出于优化目的的代码重新排序而导致。

如果你使用了 volatile 变量,一旦线程 B 从中读取了一些值 X,VM 就会确保线程 A 在写入 X 之前写入的任何内容对 B 也是可见的。(并且 A 保证可见的所有内容也可见) ,传递)。

同步块和其他类型的锁也有类似的保证。

This is simply an additional bonus the memory model gives you, if you work with volatile variables.

Normally (i.e. 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. E.g. 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.

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).

Similar guarantees are given for synchronized blocks and other types of locks.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文