繁体   English   中英

了解 Java 中同步块与 volatile 变量的原子性、可见性和重新排序

[英]Understanding atomic-ness, visibility and reordering of synchonized blocks vs volatile variables in Java

我试图从 Java Concurrency in Practice 一书中了解volatile关键字。 我在三个方面比较了synchronized关键字和volatile变量:原子性、易变性和重新排序。 我对此有一些疑问。 我在下面一一讨论过:

1) 可见性:`synchronized` 与 `volatile`

书中关于synchronized可见性说如下:

线程A在同步块中或在同步块之前所做的一切,当它执行由同一锁保护的同步块时,对B都是可见的。

它说以下关于volatile变量的可见volatile

易失性变量不会缓存在寄存器或缓存中,它们对其他处理器隐藏,因此对易失性变量的读取始终返回任何线程的最新写入。
volatile 变量的可见性影响超出了 volatile 变量本身的值。 当线程 A 写入一个 volatile 变量,随后线程 B 读取同一个变量时,在写入 volatile 变量之前对 A 可见的所有变量的值在读取 volatile 变量后对 B 可见。 所以从内存可见性的角度来看,写入一个 volatile 变量就像退出一个同步块,读取一个 volatile 变量就像进入一个同步块。

一季度。 我觉得上面的第二段( volatile )对应于书中所说的synchronized 但是是否有synchronized相当于volatile的第一段? 换句话说,使用synchronized可以确保任何/某些变量不会被缓存在处理器缓存和寄存器中?

请注意,该书还说明了以下有关synchronized可见性:

锁定不仅仅是关于互斥; 它还与内存可见性有关。

2)重新排序:`synchornized` vs `volatile`

本书在重新排序的上下文中说明了以下有关volatile的内容:

当一个字段被声明为volatile ,编译器和运行时会注意到这个变量是共享的,并且对它的操作不应与其他内存操作重新排序。

Q2。 本书没有提及在synchronized上下文中重新排序的任何内容。 有人可以解释一下在synchronized上下文中重新排序的含义吗?

3) 原子性

本书说以下关于synchronizedvolatile原子volatile

volatile 的语义不足以使增量操作( count++ )原子化,除非您可以保证变量仅从单个线程写入。

加锁可以同时保证可见性和原子性; volatile 变量只能保证可见性。

Q3。 我想这意味着两个线程可以一起看到volatile int a ,两者都会增加它然后保存它。 但是只有最后一次读取才会生效,从而使整个“读取-增量-保存”非原子性。 我对volatile非原子性的这种解释是否正确?

第 4 季度。 是否所有锁定等效项都具有可比性并具有相同的可见性、排序和原子性属性:同步块、原子变量、锁?

PS:这个问题与我几天前问过的这个问题的完全修改版本有关。 自从全面改造后,我还没有删除旧的。 我以更有针对性和结构化的方式写了这个问题。 一旦我得到这个答案,就会删除旧的。

'synchronized' 和 'volatile' 之间的主要区别在于,'synchronized' 可以使线程暂停,而 volatile 不能。

“缓存和寄存器”不是一回事。 这本书说,因为在实践中这通常是事物的实现方式,并且它使理解 JMM(Java 内存模型)的方式和原因变得更容易(或者可能不是,考虑到这些问题)。

然而,JMM 没有为它们命名。 它只是说 VM 可以自由地为每个线程提供其自己的任何变量的本地副本,或者不,在任意时间与某些或所有其他线程同步,或者不同步......除非有一个happens-before任何地方的关系,在这种情况下,VM必须确保在两个线程之间的执行点(关系建立之前发生) ,它们观察处于相同状态的所有变量。

实际上,这可能意味着刷新缓存。 或不; 这可能意味着另一个线程覆盖了它的本地副本。

虚拟机可以随意实现这些东西,而且在每个架构上都不同。 只要 VM 坚持 JMM 提供的保证,它就是一个很好的实现,因此,您的软件必须仅在这些保证下工作,而没有其他假设; 因为如果您依赖 JMM 无法保证的假设,那么在您的机器上工作的内容可能无法在另一台机器上工作。

重新排序

重新排序也不在 VM 规范中。 VM 规范中的 IS 是以下两个概念:

  1. 在单个线程的范围内,您可以从内部观察到的所有内容都与有序视图一致。 也就是说,如果你写 'x = 5; y = 10;' 不可能从同一个线程中观察到 y 是 10 而 x 是它的旧值。 无论是同步的还是不稳定的。 因此,任何时候它都可以在不可见的情况重新排序事物,那么 VM 就可以自由地进行。 会吗? 直到 VM。 有些会,有些不会。

  2. 当观察由其他线程引起的效果时,并且您还没有建立发生在之前的关系,您可能会以任何顺序看到一些、全部或没有这些效果。 真的,这里什么都可能发生。 在实践中,那么: 不要试图观察由其他线程引起的效果而不建立一个发生之前,因为结果是任意的和不可测试的

发生在各种事物建立关系之前; 同步块显然是这样做的(如果您的线程在尝试获取锁时被冻结,然后它运行,则该对象上完成“之前发生过”的任何同步块以及它们所做的任何事情您现在都可以观察到,并保证您观察与那些按顺序运行的东西一致,以及他们写的所有数据你可以看到的地方(例如,你不会得到一个旧的“缓存”或诸如此类的东西)。易失性访问也是如此。

原子性

是的,即使 x 是 volatile,您对为什么 x++ 不是原子的解释是正确的。

我不确定你的 Q4 想要问什么。

一般来说,如果你想原子地递增一个整数,或者执行许多其他并发操作中的任何一个,请查看java.util.concurrent包。 这些包含各种概念的有效和有用的实现。 例如, AtomicInteger可用于以其他线程可见的方式以原子方式递增某些内容,同时仍然非常高效(例如,如果您的 CPU 支持比较和设置(CAS)操作,则 Atomicinteger 将使用它; 不是您可以在不求助于Unsafe情况下从通用 Java 中执行的操作)。

只是为了补充 rzwitserloot 优秀的答案:

A1. 您可以这样想:一旦第一个线程退出同步块并且在另一个线程进入之前,synchronized 保证所有已兑现的更改对进入同步块(从缓存中刷新)的其他线程可见。

A2。 当且仅当 T2 在同一个守卫上同步时,同步块内的线程 T1 执行的操作在其他线程 T2 看来是未重新排序的。

A3. 我不确定你是怎么理解的。 可能会发生这样的情况,当增加两个线程时,首先读取变量a将产生一些值v ,然后两个线程将在本地增加值v的本地副本,产生v' = v + 1 ,然后两个线程都将写入v'a 因此最后的值a可以是v + 1 ,而不是v + 2

A4. 基本上是的,虽然在同步块中您可以原子地执行许多操作,而原子变量允许您只执行某个特定的单个操作,例如原子增量。 此外,不同之处在于,当不正确地使用同步块时,即通过读取同步块外部的变量,这些变量被同步块内的另一个线程修改,您可以非原子地观察它们并重新排序。 原子变量不可能实现的东西。 锁定与同步完全相同。

Q1 我觉得上面的第二段(不稳定的)对应于书中关于同步的内容。

当然。 volatile访问可视为同步精简版。

但是是否有同步等效于 volatile 的第一段? 换句话说,使用 synchronized 是否可以确保任何/某些变量不会被缓存在处理器缓存和寄存器中?

这本书通过混合级别使您感到困惑。 volatile访问与处理器缓存或寄存器没有直接关系,事实上这本书关于缓存肯定是不正确的。 波动性和同步性与某些操作的线程间可见性有关,尤其是对共享变量的写入。 如何语义实现在很大程度上是一个独立的问题。

无论如何,不​​,同步不会对变量的存储施加任何限制。 与同步语义有关的一切都发生在同步区域的边界上。 这就是为什么从一组并发运行的线程对给定变量的所有访问必须在同一对象上同步,以便程序与该变量正确同步。

本书在重新排序的上下文中说明了以下有关volatile的内容:

当一个字段被声明为volatile ,编译器和运行时会注意到这个变量是共享的,并且对它的操作不应与其他内存操作重新排序。

Q2 本书没有提及在同步上下文中重新排序的任何内容。 有人可以解释一下在同步上下文中重新排序的含义吗?

但是,这已经说的同步访问的东西(不是一切)。 您需要了解,这种意义上的“内存操作”是对共享变量的读取或写入,或者获取或释放任何对象的监视器。 进入同步区域需要获取一个监视器,所以书中已经正确地指出,不会跨同步区域的边界对volatile访问进行重新排序。

更一般地,共享变量的读取不会相对于同步区域的开头重新排序,并且写入不会相对于一个区域的结尾重新排序。

Q3 我想这意味着两个线程可以一起看到 volatile int a ,两者都会增加它然后保存它。 但是只有最后一次读取才会生效,从而使整个“读取-增量-保存”非原子性。 我对 volatile 的非原子性的这种解释是否正确?

是的。 自增运算符对应用它的变量执行读取和写入操作。 如果该变量是volatile则 volatile 语义适用于这些单独的变量,因此如果没有其他保护,则可能会在同一变量上发生其他操作。

第 4 季度 是否所有锁定等效项都具有可比性并具有相同的可见性、排序和原子性属性:同步块、原子变量、锁?

嗯? 这个子问题太宽泛了。 你正在阅读关于它的整本书。 一般来说,虽然,没有这些机制有一些共同的特点和一些不同。 所有这些都会影响内存操作的可见性及其顺序,但它们并不完全相同。 “原子性”是另外两个的函数。

暂无
暂无

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

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