【问题标题】:Does the latest JMM specify the synchronized block to be atomic to other threads even asynchronized ones?最新的 JMM 是否指定同步块对其他线程(甚至异步线程)是原子的?
【发布时间】:2015-12-24 09:34:16
【问题描述】:

当我在http://www.javaworld.com/article/2074979/java-concurrency/double-checked-locking--clever--but-broken.html 上浏览一篇关于 DOUBLE-CHECKED LOCKING 的文章时,我遇到了一条评论说“应该注意的是,实际上 DCL 可能适用于某些 JVM 的某些版本——因为实际上很少有 JVM正确实施 JMM。” 因此,我从中推断 JMM 指定同步块是原子的,即使对于其他线程中未同步的块也是如此。 我对吗? (我尝试阅读oracle网站上的JMM,但是太抽象了,我放弃了。)

【问题讨论】:

  • 同步块绝对不是原子的,我不明白你会如何从该评论中推断出这一点。
  • 不,你错了。如果您对此有疑问,请阅读 JMM 规范。任何人都可以提出一些毫无根据的猜测,但如果您没有费心阅读您正在谈论的内容,您就不能指望人们深入反驳它

标签: java multithreading java-memory-model double-checked-locking synchronized-block


【解决方案1】:

首先,请注意,Brian Goetz 在 2001 年撰写了这篇文章。在修改内存模型 implementation of JSR-133 之后,本文中描述的信息不再准确。然而,真实的是文章的示例 DCL 已损坏:

class SomeClass {

  private Resource resource = null;

  public Resource getResource() {
    if (resource == null) {
      synchronized (this) {
        if (resource == null) 
          resource = new Resource();
      }
    }
    return resource;
  }
}

使用上面的代码,当实例的构造函数还没有完全执行时,resource 字段可能被观察到不是null。问题是构造函数不能保证在字段分配之前执行,因为 JVM 可以应用代码优化。因此,构造函数调用应该被视为(在伪代码中):

resource = alloc Resource;
resource.new();

有了这些信息,我们可以看到初始检查 resource == null 是如何在 new 被调用之前为另一个线程生成 false 的,这会将不完整的实例暴露给另一个线程。这个其他线程永远不会进入同步块,也不会等待构造函数调用完成。

在今天的 Java 中,将 resource 字段设置为 volatile 就足够了。在这种情况下,DCL 确实有效,甚至非常高效,因为读取 volatile 字段是not too expensive on most hardware。 Alexey Shipilev 讨论了安全、懒惰的发布 in detail 对性能的影响。带有volatile 的 DCL 是当今的一种常见模式,例如 Scala 将其用于其 lazy 字段。

但是要回答您的实际问题:基本上,JVM 的所有实现都以比其规范更松散的方式实现内存模型。因此,非易失性 DCL 可能只在许多机器上工作,尽管由于实现细节而导致不正确的同步。但是,您永远不应针对实现进行编码,而应始终针对规范进行编码。否则,您的代码可能只会在某些时候失败,而且只会在某些机器上失败,这是一个需要追踪的可怕错误!这与 synchronized 块的原子性无关,它仅与您的 VM 如何执行代码有关,其中构造函数可能顺便总是在将您的实例公开到 resource 字段之前执行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-05
    • 2014-01-11
    • 1970-01-01
    • 2014-04-30
    • 1970-01-01
    • 1970-01-01
    • 2010-09-08
    • 1970-01-01
    相关资源
    最近更新 更多