java - 了解 Java 中同步块(synchronized block)与 volatile 变量的原子性、可见性和重新排序

标签 java multithreading

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

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

关于 synchronized 的可见性,本书说如下:

Everything thread A did in or prior to a synchronized block is visible to B when it executes a synchronized block guarded by the same lock.

关于 volatile 变量的可见性如下:

Volatile variables are not cached in registers or in caches where they are hidden from other processors, so a read of a volatile variable always returns the most recent write by any thread.
The visibility effects of volatile variables extend beyond the value of the volatile variable itself. 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. So from a memory visibility perspective, writing a volatile variable is like exiting a synchronized block and reading a volatile variable is like entering a synchronized block.

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

请注意,这本书还对 synchronized 的可见性进行了以下说明:

Locking is not just about mutual exclusion; it is also about memory visibility.

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

本书在重新排序的上下文中对 volatile 进行了以下说明:

When a field is declared volatile, the compiler and runtime are put on notice that this variable is shared and that operations on it should not be reordered with other memory operations.

Q2. 本书没有提及在 synchronized 的上下文中重新排序。有人可以解释在 synchronized 的上下文中重新排序是什么意思吗?

3) 原子性

本书讲述了 synchronizedvolatile 的原子性。

the semantics of volatile are not strong enough to make the increment operation (count++) atomic, unless you can guarantee that the variable is written only from a single thread.

Locking can guarantee both visibility and atomicity; volatile variables can only guarantee visibility.

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

Q4.是否所有的锁定等效项都具有可比性并具有相同的可见性、顺序和原子性属性:同步块(synchronized block)、原子变量、锁?

PS:这个问题与 this 的完全修改版本有关。几天前我问的问题。自从全面改版以来,我还没有删除旧版本。我以更集中和更有条理的方式写了这个问题。一旦我得到这个答案,就会删除旧的。

最佳答案

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

'缓存和寄存器'不是一个东西。这本书说,因为在实践中这通常是实现事物的方式,并且它更容易(或者可能不是,鉴于这些问题)理解 JMM(java 内存模型)的方式和原因。

但是,JMM 没有为它们命名。它只是说虚拟机可以自由地给每个线程自己的任何变量的本地副本,或者不,在任意时间与一些或所有其他线程同步,或者不...... 除非 任何地方都有发生之前的关系,在这种情况下,VM 必须确保在两个线程之间的执行点 happen before 关系已经建立,他们观察同一状态下的所有变量。

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

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

重新排序

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

  1. 在单个线程的范围内,你可以从它内部观察到的一切都与有序 View 一致。也就是说,如果你写 'x = 5; y = 10;'不可能从同一个线程中观察到 y 是 10 但 x 是它的旧值。不管是同步的还是易失的。因此,只要它可以重新排序事物不可见,那么 VM 就可以自由地进行。会吗?直到虚拟机。有些会,有些不会。

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

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

原子性

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

我不确定您的 Q4 想问什么。

一般来说,如果你想原子地增加一个整数,或者做任何其他并发式的操作,看看 java.util.concurrent 包。这些包含各种概念的有效和有用的实现。 AtomicInteger ,例如,可用于原子地增加某些东西,以一种对其他线程可见的方式,同时仍然非常高效(例如,如果您的 CPU 支持比较与设置(CAS)操作,Atomcinteger 将使用它;不借助 Unsafe 就不能从一般的 java 中完成)。

关于java - 了解 Java 中同步块(synchronized block)与 volatile 变量的原子性、可见性和重新排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60970865/

相关文章:

java - java中如何将一个数转换成2的补数

java - 使用java中的Document删除xml中的节点

rest - 如何将 HashSet 传递到服务器以测试 postman 的 API?

每天Java调度

android - 如何在 doInBackground 方法中调用 UI 或 UI 线程

c# - 卡住 GUI 元素

c++ - g_main_loop_run 阻塞 Qthread 并且不允许停止视频

java:使用Raw类型作为方法参数会删除参数成员中的所有参数化类型信息

c# backgroundWorker 不引发 ProgressChanged 或 RunWorkerCompleted 事件

python-3.x - PyQt5 + Python 3 : passing lists, dicts 作为跨线程的信号参数