代码之家  ›  专栏  ›  技术社区  ›  Denis Bazhenov

相对于其他字段的不稳定语义

  •  15
  • Denis Bazhenov  · 技术社区  · 16 年前

    private volatile Service service;
    
    public void setService(Service service) {
      this.service = service;
    }
    
    public void doWork() {
      service.doWork();
    }
    

    Service 实施一分钟)。

    这是否意味着以下代码是正确的?

    private volatile boolean serviceReady = false;
    private Service service;
    
    public void setService(Service service) {
      this.service = service;
      this.serviceReady = true;
    }
    
    public void doWork() {
      if ( serviceReady ) {
        service.doWork();
      }
    }
    
    4 回复  |  直到 11 年前
        1
  •  17
  •   Cowan    16 年前

    是的,从Java 1.5开始,这段代码是“正确的”。

    原子性不是一个问题,无论是否使用volatile(对对象引用的写入都是原子性的),所以无论哪种方式,你都可以将其从关注点列表中划掉——唯一悬而未决的问题是更改的可见性和排序的“正确性”。

    对volatile变量的任何写入都会建立“发生在之前”的关系(新Java内存模型的关键概念,如中所述 JSR-133 )以及对同一变量的任何后续读取。这意味着读取线程必须能够看到写入线程可见的所有内容:也就是说,它必须看到所有具有 至少 它们在写入时的“当前”值。

    我们可以通过查看以下内容来详细解释这一点 section 17.4.5 of the Java Language Specification ,具体如下要点:

    1. “如果x和y是同一线程的动作,并且x在程序顺序上位于y之前,那么hb(x,y)”(也就是说,同一线程上的动作不能以与程序顺序不一致的方式重新排序)
    2. “对volatile字段的写入(§8.3.1.4)发生在该字段的每次后续读取之前。”(这是澄清文本,解释volatile域的写入然后读取是一个同步点)
    3. “如果hb(x,y)和hb(y,z),那么hb(x,z)”(之前发生的传递性)

    因此,在您的示例中:

    • 根据规则1,写入“service”(a)发生在写入“serviceReady”(b)之前
    • 根据规则2,对“serviceReady”(b)的写入发生在读取(c)之前
    • 因此,(a)发生在(c)之前(第3条规则)

    这意味着一旦serviceReady为true,您就可以保证“服务”设置正确。

    你可以看到一些好的文章使用几乎 确切地 IBM DeveloperWorks --请参阅“挥发性物质的新保证”:

    在写入V时对A可见的值现在保证对B可见。

    一个在 the JSR-133 FAQ ,由该JSR的作者撰写:

    因此,如果读者看到v的值为true,那么也可以保证看到之前发生的对42的写入。在旧的内存模型下,这是不正确的。如果v不是volatile,那么编译器可以对writer中的写入进行重新排序,读者对x的读取可能会看到0。

        2
  •  2
  •   Kutzi    16 年前

    如果这是正确的代码。

    @CPerkins:制造唯一 setService 方法synchronized不起作用,因为您还必须对读取进行同步。

    然而,在这种情况下,一个变量就足够了。为什么需要额外的布尔字段。 例如

    private volatile Service service;
    
    public void setService(Service service) {
      this.service = service;
    }
    
    public void doWork() {
      if ( service != null ) {
        service.doWork();
      }
    }
    

    鉴于从来没有人调用setService null 。因此,您可能应该进行空检查:

    private volatile Service service;
    
    public void setService(Service service) {
      if (service == null) throw NullPointerException();
      this.service = service;
    }
    
    public void doWork() {
      if ( service != null ) {
        service.doWork();
      }
    }
    
        3
  •  1
  •   CPerkins    16 年前

    你说得对 volatile ,所以这应该是正确的,但我对你的设计感到困惑。我不明白你为什么不直接同步 setService -它可能不会经常被调用。如果它被调用多次,“ if (serviceReady) “这部分没有意义,因为它仍然是真的,但这很好,因为如果我理解正确的话,替换是原子性的。

    我推测 service.doWork() 线程安全吗?

        4
  •  0
  •   Community Mohan Dere    9 年前

    volatile read 在第一个。volatile只读保证读取线程看到变量的最新值。因此,它绝对没有进入锁定(同步)部分那么强大。

    在实践中,它可能会起作用,具体取决于您使用的JVM对volatile的实现。如果通过刷新所有CPU缓存来实现易失性读取,它应该会起作用。但我敢打赌,这不会发生。 Can I force cache coherency on a multicore x86 CPU? 是一本关于这个话题的好书。

    我会说,只需为这两个变量设置一个公共锁(java.util.concurrent.lock或synchronized),然后就可以完成了。


    Java Language Specification, Third Edition ,关于volatile有以下说法:

    8.3.1.4易挥发油田

    字段可以声明为volatile,在这种情况下,Java内存模型(第17节)确保所有线程都能看到变量的一致值。

    • 对volatile变量(§8.3.1.4)v的写入与任何线程对v的所有后续读取同步(其中后续读取根据同步顺序定义)。
    • 对易失性字段(§8.3.1.4)的写入发生在该字段的每次后续读取之前。

    它没有提到其他变量的可见性效应。