【问题标题】:Java volatile and side-effectsJava volatile 和副作用
【发布时间】:2012-02-28 11:11:31
【问题描述】:

Oracle 关于原子访问的文档(http://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html)这样说:

“一个volatile变量建立了happens-before关系……这意味着……当一个线程读取一个volatile变量时,它看到的不仅仅是volatile的最新变化,还有代码的副作用这导致了变化。”

我很难理解这一点。我了解 volatile 变量是如何工作的(在 >= Java 5 中),但我想知道 java 如何决定什么副作用“导致”了 volatile 变量的变化。

所以我想我的问题是:这个保证有什么副作用?

编辑:

所以我了解到,如果线程 A 修改了一个 volatile 变量,然后线程 B 读取它,那么在写入 volatile 变量之前发生的线程 A 的所有写入相对于线程 B 都是“一致的”(即受线程 A 上述写入影响的变量的缓存值在线程 B) 中无效。如果我错了,请纠正我。

【问题讨论】:

  • 您确实对缓存的工作方式有误解,但 volatile write 会刷新 写入缓冲区 而不是缓存(可以手动刷新缓存,但通常您永远不会想要)跨度>
  • 我的误解到底是什么?听起来您的答案与 Tom Hawtin 的不同 - 也许您想写一个替代答案?
  • 不,这与他的回答并不矛盾,尽管它(他的回答)并不完全完整。缓存不会被刷新(这只会破坏任何性能),但只有修改后的缓存行才会保持一致[简单地说]。它还确保没有投机性读取过去写入(这是非常重要的)。因此,当 CPU 恢复时,所有其他人将/可能知道该写入(以及所有先前的写入)。一般来说,所有 CPU 都在 某个 点使写入可用/可见,但使用 volatile 写入它只是 现在。我不认为 SO 是解释缓存一致性如何工作的好领域。
  • 顺便说一句,“冲洗”是指“驱逐”吗?刷新缓存通常意味着驱逐其条目,而不仅仅是传播写入。
  • 我不清楚我的意思。忽略那句话。问题:“过去写”是什么意思?

标签: java concurrency atomic volatile side-effects


【解决方案1】:

大多数多处理器缓存都具有一致性机制,因此惩罚并不像刷新所有缓存那么糟糕。

在这样做之前写入 volatile 的线程中的任何写入都将被读取 volatile 之后的线程看到。

【讨论】:

  • 啊,这很有意义。那么该特定线程的缓存中的所有内容都将被刷新吗? Java 没有办法表明一个变量肯定不会被另一个线程访问(因此不需要刷新)?或者编译器/解释器可能会围绕它执行某种优化?
  • 另外,我喜欢你的措辞“在这样做之后” - 所以从线程 A 读取到线程 B 中已更改的非易失性可能已经过时,直到线程 A 读取线程更改的易失性B,对吗?
  • @BT 我不是缓存一致性方面的专家。没有其他缓存“认为”它具有缓存行的干净副本是必要的。当然没有必要使一切无效。局部变量可以保留在寄存器中。有一些机会可以保留堆分配的对象,但这几乎可以忽略不计。 / 是的。
  • @BT 这完全取决于使用的缓存一致性协议。一般来说,我们只需要传播所有修改过的缓存行的写入(如果协议提供该信息 - 不知道 ARM,但更复杂的桌面 CPU 肯定会这样做)而不是整个缓存。
【解决方案2】:

Double Checked Locking 为例。当你创建一个对象时,很多事情都会在幕后发生:

MyClass c=new MyClass();

分配内存,调用构造函数,并将内存位置分配给变量c。 JVM 可以重新排序这些操作。如果在调用构造函数之前分配了内存,分配了值,并且线程中断并使用了该值,则会导致问题。

volatile MyClass c=new MyClass();

根据 1.5 规则,分配保证是这些事件中的最后一个。 “副作用”是分配和构造函数调用。

【讨论】:

  • 这是一个合理的例子,但它并没有解释 java 如何决定传播什么方面或何时传播。汤姆的回答非常简洁地解释了这一点。
猜你喜欢
  • 1970-01-01
  • 2016-09-14
  • 1970-01-01
  • 2017-03-13
  • 2015-07-20
  • 1970-01-01
  • 2013-12-30
  • 2011-03-31
  • 2014-02-03
相关资源
最近更新 更多