能见度保证

Visibility Guarantee

我看了JCIPsection 16.3 "Initialization Safety"的一些解释,还是不太清楚。该部分指出

"Further, any variables that can be reached through a final field of a properly constructed object (such as the elements of a final array or the contents of a HashMap referenced by a final field) are also guaranteed to be visible to other threads."

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

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?

干杯

更新

我注意到关于 class 有吸气剂存在一些混淆。 我正在更新源代码,希望这会很清楚。 感谢大家的观看。

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 class 中的字段不是最终的,所以没有可见性保证。

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

中有可见性保证

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

c) 在客户端 class 中将对 Container 的引用声明为 volatile 不会解决与引用相关的字段的可见性问题。

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

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

An object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.

The usage model for final fields is a simple one: Set the final fields for an object in that object's constructor; and do not write a reference to the object being constructed in a place where another thread can see it before the object's constructor is finished. If this is followed, then when the object is seen by another thread, that thread will always see the correctly constructed version of that object's final fields. It will also see versions of any object or array referenced by those final fields that are at least as up-to-date as the final fields are.

虽然这里的措辞含糊,我认为“正确初始化值”和“最新字段" 意味着如果你将 c 传递给 Thread1 构造函数之外的 Thread2Thread2 将始终看到一个完整构造的 Container 实例及其字段已初始化。

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

应不惜一切代价避免不安全的发布,除非您真的需要它并且您确切地知道自己在做什么。

A​​ class 通常不需要设计来承受不安全的发布,除非有充分的理由。例如,String 就是这样设计的,因为它在核心 security/access 控制代码中被广泛使用,并且字符串的内容必须看起来是常量,即使一些恶意程序试图破坏它不安全的发布。

大多数 classes 不需要使用 final 字段来抵御不安全的发布。

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