【问题标题】:Java Memory Model interaction of synchronization, volatile and (stamped) locksJava 内存模型与同步、易失和(标记)锁的交互
【发布时间】:2018-09-20 23:24:57
【问题描述】:

在使用锁时是否需要volatile 修饰符以保证内存可见性?

试图完全理解并发性、内存可见性和执行控制我遇到了几个消息来源,说在synchronized 块中更新的变量不需要字段是volatile(大多数情况下没有给出来源,实际上有一页说同步方法和波动率字段需要结合使用)。

接近jls chapter 17.4.5时发现:

两个动作可以通过happens-before关系排序。如果一个动作 发生在另一个之前,那么第一个对第二个可见并在第二个之前排序。

这部分是否说后续同步方法调用保护相同的变量变量将确保它对第二个线程可见?如果是这种情况,由于我们也可以保证订单,因此锁也同样适用吗?

另一方面,当我们突然拥有允许 2 个线程访问该字段的写锁时会发生什么。即使变量被解锁,整个构造是否崩溃并且线程永远无法保证更新其缓存?

简短的代码

int field; //volatile not needed because we have a definite happens-before relationship
Lock lock;

void update(){
    //No matter how many threads access this method they will always have 
    //the most up to date field value to work with.
    lock.lock()
    field *= 2;
    lock.unlock();
}

【问题讨论】:

  • variables updated in synchronized blocks do not require the field to be volatile 这是正确的。
  • 对紧接在此之前的问题:If this is the case does the same hold true for locks since we can also guarantee an order? 是的,如果文档说锁具有内存可见性副作用,则也是。 大多数锁来自 @ 987654328@ 在他们的 API 文档中有这样的注释。
  • 您的“短代码”仍然不是线程安全的。因为您使用锁来同步更新,但您未能保护实例变量 field 免受其他线程读取它并执行复合操作。使您的“短代码”线程安全。您需要 volatile 实例字段或封装它们并同步访问器方法。
  • 一个读写锁只允许多个读者。这些读取器与写入器的最后一次更新仍然具有发生前的排序关系。

标签: java multithreading concurrency java-memory-model


【解决方案1】:

来自Lock 的 API 文档:

https://docs.oracle.com/javase/10/docs/api/java/util/concurrent/locks/Lock.html

所有 Lock 实现都必须强制执行相同的内存同步 由内置监视器锁提供的语义,如中所述 Java™ 语言规范第 17 章:

  • 成功的锁定操作与成功的锁定操作具有相同的内存同步效果。
  • 成功的解锁操作与成功的解锁操作具有相同的内存同步效果。

锁定和解锁操作不成功,可重入 锁定/解锁操作,不需要任何内存 同步效果。

这有点不清楚 imo,但它的要点是,是的,Lock 需要以与监视器相同的方式工作(synchronized 关键字的作用),因此您的示例总是会进行最新更新的 field 可见,而无需明确使用 volatile 关键字。

附:获取 Brian Goetz 的 Java Concurrency in Practice,它更详细地解释了所有这些内容。它基本上是 Java 中所有并发的圣经。

【讨论】:

  • 感谢您的宝贵时间。如果没有什么非常新的表面,我会在今天晚些时候接受你的回答
  • 别担心,花点时间研究一下。
【解决方案2】:

...实际上有一页说同步方法和波动率字段需要结合使用。

您可以将有关内存可见性和synchronized 块的所有信息提炼成一个简单的规则。也就是说,线程 A 在从 synchronized (o) {...} 块退出之前对共享变量和对象所做的任何事情都保证在线程 B 进入同一对象的 synchronized (o) {...} 块时对线程 B 可见, em> o.

而且,正如@markspace 已经说过的,java.util.concurrent.locks.Lock 的任何实现都需要以相同的方式工作。

【讨论】:

    【解决方案3】:

    在使用锁时是否需要 volatile 修饰符以保证内存可见性?

    volatile 变量只保证内存可见性而不保证原子性。这是 volatile 和 Java 中的 synchronized 块之间的主要区别之一。所以当你使用synchronized 块时,变量不必是volatile。但是如果您的变量是 volatile 并且对该变量执行任何复合操作,那么您需要使用锁来保护对 volatile 变量的更新。

    这是不是说后续同步方法调用保护同一个变量将确保它对第二个线程可见?如果是这种情况,由于我们也可以保证订单,因此锁也同样适用吗?

    是的。因为锁会给你可见性和原子性。

    另一方面,当我们突然拥有允许 2 个线程访问该字段的写锁时会发生什么。即使变量被解锁,整个构造是否崩溃并且线程永远无法保证更新其缓存?

    如果您在同一个锁上保护对变量的更新,则在任何给定时间只有一个线程可以处理该变量。所以它保证了一致性。但是如果你每次都使用不同的锁来保护那个变量,那么不止一个线程会修改变量状态,并且可能使变量状态不一致。因此,在这种情况下,可见性和原子性都得到了保证,但仍然可能导致不一致。

    【讨论】:

      猜你喜欢
      • 2017-02-17
      • 2013-01-06
      • 1970-01-01
      • 2010-09-14
      • 2012-01-04
      • 2015-07-26
      • 1970-01-01
      • 2016-04-04
      • 2021-10-24
      相关资源
      最近更新 更多