可见性保证

时间:2015-09-08 18:07:33

标签: java multithreading concurrency memory-visibility

我已经从JCIP中读到了对第16.3节“初始化安全性”的一些解释,但仍然不清楚。该部分指出

“此外,通过正确构造的对象的最终字段(例如最终数组的元素或最终字段引用的HashMap的内容)可以到达的任何变量也可以保证对其他线程可见。“

所以,如果我有以下可变对象:

public final class Container{
    private String name;
    private int cupsWon;
    private double netWorth;

        public Container( String name, int cupsWon, double netWorth ){
             this.name = name;
             this.cupsWon = cupsWon;
             this.netWorth = netWorth;
        }

    //NO Setters
    //Getters
}

然后,主题1 将其创建为以下内容并将c传递给 Thread2

final Container c = new Container("Ted Dibiasi", 10, 1000000);

Thread2 (不兼容,1小时后说),读取c的值,是否有可能看到Thread2

c.name=null or
c.cupswon=0 or worst of all, 
c.netWorth=0.0?

干杯

更新

我注意到有关吸气鬼的课程有些困惑。 我正在更新源代码,希望这将是明确的。 谢谢大家一起来看看。

public final class Container{

    private String name;
    private int cupsWon;
    private double netWorth;

    public Container( String name, int cupsWon, double netWorth ){
        this.name = name;
        this.cupsWon = cupsWon;
        this.netWorth = netWorth;
    }

    public final String getName(){
        return name;
    }

    public final int getCupsWon(){
        return cupsWon;
    }

    public final double getNetWorth(){
        return netWorth;
    }

}

// ----------

public final class Producer{

    private final Client client;

    public Producer( Client client ){
         this.client = client;
    }

    //Thread1 call produce()   
    public final void produce( ){
        final Container c = new Container("Ted Dibiasi", 10, 1000000);
        client.update( c );
    }

}

// ----

public final class Client{

     private Container c;
     //private volatile Container c;       

     public final void update( Container c ){
          this.c = c;
     }

     //Thread2 calls consume().
     public final void consume( ){
          String name = c.getName();
          int cupsWon = c.getCupsWon();
          double netWorth = c.getNetWorth();           
     }

 }

我的问题是:

a)当 Thread2 调用consume()时,name,cupsWon,netWorth可以为null,0还是0.0?我的想法是它 CAN 因为Container类中的字段不是最终的,所以没有可见性保证。

b)然而,然后我阅读了第16.3节和关于“可以通过正确构造的对象的最终字段到达的变量”的位,这是否意味着因为Container c的实例声明是最终的,我们 DO 在consume()中有可见性保证吗?

最终容器c =新容器(“Ted Dibiasi”,10,1000000);

c)在Client类中声明对Container的引用为volatile,不会解决与引用相关的字段的可见性问题。

3 个答案:

答案 0 :(得分:6)

final Container c = new Container("Ted Dibiasi", 10, 1000000);

如果c这里是Thread1中的最后一个字段而不是局部变量,那么来自Java Language Specification的引用适用于此最终字段c

  

当构造函数完成时,对象被认为是完全初始化的。在该对象完全初始化之后只能看到对象引用的线程可以保证看到该对象的最终字段的正确初始化值

     

最终字段的使用模型很简单:在该对象的构造函数中设置对象的最终字段;并且在对象的构造函数完成之前,不要在另一个线程可以看到的地方写入对正在构造的对象的引用。如果遵循此原因,那么当另一个线程看到该对象时,该线程将始终看到该对象的最终字段的正确构造版本。 它还会看到那些最终字段引用的任何对象或数组的版本,这些字段至少与最终字段一样是最新的。

虽然这里的措辞含糊不清,但我认为“正确初始化值”和“最新字段”意味着如果你通过了在c构造函数之外的Thread2 Thread1Thread2将始终看到一个完整构建的Container实例,其字段已初始化。

答案 1 :(得分:0)

程序员通常不需要担心这个问题。如果一个对象被不安全地发布"这只是一个问题,例如,该对象被Thread-1分配给一个非易失性静态字段,而Thread-2从读取该对象中检索该对象不稳定的。然而,这种情况很少发生;对象在线程之间传递,几乎总是带有一些内存屏障。例如,在将对象传递给ThreadPoolExecutor时,您不必担心可见性。

应该不惜一切代价避免不安全的出版,除非你真的需要它,而且你确切地知道你在做什么。

除非有充分的理由,否则课程通常不需要设计为能够承受不安全的出版物。例如,String是以这种方式设计的,因为它在核心安全/访问控制代码中被广泛使用,并且字符串的内容必须看起来是不变的,即使某些恶意程序试图通过不安全的发布来破坏它

为了抵御不安全的发布,大多数类不需要使用final字段。

答案 2 :(得分:0)

要回答你的问题,不,Thread2永远不会看到Container的字段处于未初始化状态。原因是Container的构造函数在之前完全运行,而引用Container c变得可访问。调用Client.updateClient.consume之间可能存在竞争条件。根据此种族的结果,字段cc.getName()中调用Client.consume时为null或完全初始化的Container对象。在第一种情况下,您将获得NullPointerException,在第二种情况下,您将获得正确初始化的值。我不认为它与JCIP引用的句子有任何关系。