6

我已经阅读了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 ms 之后),读取 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?我的想法是它可以,因为 Container 类中的字段不是最终的,因此没有可见性保证

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

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

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

4

3 回答 3

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

如果c这里是 final 字段而不是局部变量,那么Java 语言规范Thread1的引用适用于这个 final 字段:c

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

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

虽然这里的措辞含糊不清,但我认为“正确初始化的值”和“最新的作为最终字段”意味着如果您c将初始化。Thread2Thread1Thread2Container

于 2015-09-08T19:46:34.817 回答
0

程序员通常不需要担心这个问题。仅当对象“不安全地发布”时才会出现问题,例如,对象由 Thread-1 分配给非易失性静态字段,而 Thread-2 通过读取非易失性字段来检索对象。但是,这种情况很少见;对象在线程之间传递几乎总是带有一些内存屏障。例如,当您将对象传递给 ThreadPoolExecutor 时,您无需担心可见性。

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

一个类通常不需要设计成能够承受不安全的发布,除非有充分的理由。例如,String之所以这样设计,是因为它在核心安全/访问控制代码中广泛使用,并且字符串的内容必须看起来是恒定的,即使某些恶意程序试图通过不安全的发布来破坏它。

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

于 2015-09-08T20:23:22.630 回答
0

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

于 2015-09-21T13:55:31.857 回答