Java中的易变variables

所以我正在阅读这本题为“ 实践中的Java并发”的书,我被困在这样一个我不能理解的例子中。 这是引用:

当线程A写入一个易失性variables,然后线程B读取同一个variables时,写入易失性variables之前AA可见的所有variables的值在读取volatilevariables后对B变得可见。

有人能给我一个反例,说明为什么“在写入易失variables之前AA可见的所有variables的值在B读完易失variables后变为可见”?

我很困惑,为什么在读取volatilevariables之前,所有其他的非易失性variables都不能被B看到?

线程B可能具有这些variables的CPU本地caching。 读取volatilevariables可确保从先前写入到volatile的任何中间高速caching都被清除。

例如,阅读下面的链接,以“使用易失性固定双重lockinglocking”结束:

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

声明一个易变的Javavariables意味着:

  • 这个variables的值永远不会被caching在本地线程中:所有的读写操作都将直接进入“主内存”。
  • 对variables的访问就好像封装在一个同步块中一样,同步于自身。

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

当多个线程使用相同的variables时,每个线程将拥有自己的该variables的本地caching副本。 所以,当它更新值时,它实际上在本地caching中更新,而不是在主variables内存中更新。 使用相同variables的另一个线程不知道另一个线程更改的值。 为了避免这个问题,如果你声明一个variables为volatile,那么它将不会被存储在本地caching中。 每当线程更新值时,它都会更新到主内存。 所以,其他线程可以访问更新的值。

来自JLS§17.4.7 良好的执行

我们只考虑格式正确的执行。 如果满足以下条件,则执行E = <P,A,po,所以,W,V,sw,hb>

  1. 每次读取都会在执行过程中看到写入同一个variables。 所有易失性variables的读写操作都是不稳定的。 对于A中的所有读取r,我们有W(r)在A和W(r)中.v = rvvariablesrv是易失性的当且仅当r是一个易失性读取,并且variableswv是易失性的当且仅当w是一个易变的写。

  2. 发生 – 订单之前是部分订单。 发生 – 在顺序是由同步的传递闭包给出的 – 边和程序顺序。 它必须是一个有效的偏序:自反,传递和反对称。

  3. 执行服从于线程内一致性。 对于每个线程t,A中由t执行的动作与该线程按程序顺序隔离生成的行为相同,每个写入写入值V(w),假定每个读取r读取值V W(R))。 每次读取的值由内存模型决定。 给出的程序顺序必须反映按照P的线程内语义进行动作的程序顺序。

  4. 执行是一致的(§17.4.6)。

  5. 执行服从同步顺序一致性。 对于A中的所有易失性读取r,并非如此(r,W(r))或存在写赢W使得wv = rv等(W(r),w)等等w,r)。

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

如果一个variables是非易失性的,那么编译器和CPU可以按照他们认为合适的方式自由地重新sorting指令,以优化性能。

如果variables现在被声明为volatile,那么编译器不再尝试优化对该variables的访问(读取和写入)。 但是,可能会继续优化其他variables的访问权限。

在运行时,当访问易失性variables时,JVM会为CPU生成适当的内存屏障指令。 内存屏障具有相同的目的 – CPU也可以防止重新sorting指令。

当一个volatilevariables被写入(通过线程A)时,写入任何其他variables的所有写入操作都会完成(或者至less会出现),并在写入volatilevariables之前对A可见; 这通常是由于内存写入屏障指令。 同样,任何其他variables的读取,在读取之前(由线程B)完成(或似乎是)。 这通常是由于存储器读取屏障指令。 由屏障强制执行的指令顺序,意味着对A可见的所有写入操作都将是可见的B.但这并不意味着没有发生任何指令重新sorting(编译器可能已经执行对其他指令重新sorting); 它只是意味着如果发生了对A可见的任何写入操作,那么B就可以看到这些写入操作。简而言之,这意味着严格的程序顺序不会被维护。

如果您想了解JVM如何发布内存屏障指令,我将在内存障碍和JVM并发性上指出这一点。

相关问题

  1. 什么是记忆围栏?
  2. 处理器用来优化代码的一些技巧是什么?

线程被允许caching其他线程自读取以来更新的variables值。 volatile关键字强制所有线程不caching值。

如果你使用volatilevariables,这只是内存模型给你的额外奖励。

通常(即在没有易失variables和同步的情况下),虚拟机可以按照自己想要的顺序将一个线程的variables显示给其他线程,或者根本不显示。 例如,阅读线程可以读取其他线程variables赋值的早期版本的混合。 这是由于线程可能在具有自己的caching的不同CPU上运行,这些caching有时仅被复制到“主内存”,另外通过用于优化目的的代码重新sorting。

如果你使用了一个volatilevariables,那么只要线程B从它读取到一个X值,VM就会确保线程A在写入X之前写入的任何内容对于B也是可见的(也是A被保证为可见的,transitively)。

给同步块和其他types的锁提供了类似的保证。