当前位置: 首页 > 知识库问答 >
问题:

锁定vs同步vs原子变量vs易失性在java读写原子性,可见性和保护重排序

闾丘选
2023-03-14

我从《实践中的Java并发》(Java Concurrency in Practice)一书中读到了以下关于volatile的内容:

当一个字段被声明为volatile时,编译器和运行时会注意到这个变量是共享的,对它的操作不应该与其他内存操作一起重新排序。易失性变量不会缓存在寄存器或缓存中,在这些寄存器或缓存中它们对其他处理器是隐藏的,因此对易失性变量的读取总是返回任何线程最近的写入。

可变变量的可见性影响超出了可变变量本身的值。当线程A写入易失性变量,然后线程B读取该变量时,在写入易失性变量之前对A可见的所有变量的值在读取易失性变量后对B可见。因此,从内存可见性的角度来看,写入易失性变量就像退出同步块,读取易失性变量就像进入同步块。

我对上面最后一句话感到困惑。假设变量x被定义为volatile,在修改x之前,uvw对线程A可见,那么当线程B读取x之后,它还能够读取uvw的最新值。我们可以为同步的指定相同的吗?。

问题1。也就是说,是否低于正确值?

当退出synchronized块时,线程A可以看到变量uvw,然后线程B进入synchronized块时,线程A可以看到uvw的最新值。

我觉得上述事实是不正确的,因为uvw可能存储在缓存和寄存器中,因为它们没有定义volatile。我说的对吗?因此,同步(以及原子变量,因为它们类似于同步),无法确保可见性

这本书还说:

锁定可以保证可见性和原子性;易变变量只能保证可见性。

但我觉得:

  1. 锁、synchronized和原子变量只保证读写原子性(而不是可见性和防止重新排序)

问题2。以上两点我说得对吗?


共有2个答案

蔺德曜
2023-03-14

希望以下是准确的...这是我目前尽可能简单但不简单的理解......

问题1。也就是说,是否低于正确值?

当退出synchronized块时,线程A可以看到变量uvw,然后线程B进入synchronized块时,线程A可以看到uvw的最新值。

我假设这里的“最新值”实际上是指“退出同步块时的最新值”...

然后是的——当然要注意的是,AB必须在同一个对象上同步。

附带说明:当然,类似的警告适用于可见性保证的易失性-AB必须写入和读取(分别)相同的易失性字段。

但我觉得:

  1. 锁、synchronized和原子变量只保证读写原子性(而不是可见性和防止重新排序)
  2. volatile保证可见性,防止编译器和运行时重新排序(而不是读写原子性)

问题2。以上两点我说得对吗?

对#2正确但对#1不正确...

同步的保证了可见性和原子性。“可见性”的概念也被描述为发生在发生之前的关系。

换句话说,线程A退出synchronized(x)发生在线程B进入synchronized(x)之前。

类似地,写入volatile字段x发生在读取volatile字段x之前。

换句话说,关于可见性,synchronizedenter/exit对提供与volatile读/写对完全相同的保证。

但是synchronized对保证了可见性和原子性,而volatile对只保证可见性。

Oops-忘记了一个例外:volatile longvolatile double确实保证了这些64位值的读写是原子的(即避免“字撕裂”)。

另一种看待它的方式是:在volatile字段x周围有一个小的synchronized(x'),其中,x'是与x相对应的一些不可见锁对象(这不完全相同,因为对于volatile,您必须将读取与写入配对,而所有同步的关键字的工作方式相同)。

我觉得上述事实是不正确的,因为u、v和w可能存储在缓存和寄存器中,因为它们没有被定义为易失性。我这样说对吗?

这有点令人惊讶,但是synchronizedvolatile提供的可见性保证适用于两个线程可见的所有内容,并且不仅限于被锁定的对象、volatile字段本身、同一对象中的其他字段等。

这就是为什么如果你熟悉低级程序集/内核编程等,从内存障碍的角度考虑它们是有意义的。

莫誉
2023-03-14

1) 锁、同步变量和原子变量保证了读写原子性和可见性,并防止重新排序

2)易失性保证可见性和防止编译器和运行时重新排序

易失性字段的读写原子性有点棘手:对易失性字段的读写是原子性的,例如,如果在32位jvm上写入易失性长(64位),读写仍然是原子性的。你总是读完整的64位。但对易失性int或long的操作不是原子操作

 类似资料:
  • 读了很多关于易失性、原子性和可见性的文章后,有一个问题仍然存在。以下跨线程工作,当更新/读取“B”时,“A”始终可见: 原子变量是独立的对象,这同样适用吗?下面的操作会起作用吗? 如果答案是否定的,那么扩展AtomicInteger类并在其中包含“a”就可以了,因为AtomicInteger包装了一个volatile。

  • 也许我对概念感到困惑,但是重写和在子类中创建一个新方法之间有什么区别呢?重写不就是在子类中创建一个不同于父类的新的特定方法吗?但这难道不是在子类中创建一个新方法所要做的吗?

  • 问题内容: 当它适合使用原语(例如,或),而不是,或者,反之亦然? 问题答案: 可见性语义完全相同,当需要使用原子原语时,使用原子原语很有用。 例如: 可能在多线程环境中产生问题,因为变量可能会在两行之间变化。如果您需要测试和分配是原子的,则可以使用:

  • 也许我对概念感到困惑,但是重写和在子类中创建一个新方法之间有什么区别呢?重写不就是在子类中创建一个不同于父类的新的特定方法吗?但这难道不是在子类中创建一个新方法所要做的吗?

  • 我对下面的代码段有一个问题。结果可能有一个结果[0,1,0](这是用JCStress执行的测试)。那么这是怎么发生的呢?我认为数据写入(data=1)应该在Actor2(guard2=1)中写入到guard2之前执行。我说得对吗?我问,因为很多时候我读到挥发物周围的说明没有重新排序。此外,根据这一点:http://tutorials.jenkov.com/java-concurrency/vola

  • 我试图比较两个主要Java实现的性能:Oracle和IBM运行以下测试: 通过以下方式运行上述代码: IBMJRE 1.8.0 java版本“1.8.0”java(TM)SE运行时环境(build pwa6480sr3fp22-20161213\U 02(SR3 FP22))IBM J9 VM(build 2.8,JRE 1.8.0 Windows 10 amd64-64压缩引用20161209\