不可变对象在用Java完全初始化后是否已发布?

时间:2016-03-08 18:23:28

标签: java multithreading concurrency immutability

我正在阅读Fred Long的 Java Concurrency Guidelines 中的不可变对象和线程安全性。

以下是本书的代码段。

// Immutable Helper
public final class Helper {
    private final int n;

    public Helper(int n) {
        this.n = n;
    }
    // ...
}

// and a mutable Foo class:
final class Foo {
    private Helper helper;

    public Helper getHelper() {
        return helper;
    }

    public void setHelper(int num) {
        helper = new Helper(num);
    }
}

代码段后面是解释:

  

getHelper()方法发布可变助手字段。因为   Helper类是不可变的,它不能被改变   初始化。此外,因为Helper是不可变的,所以它总是如此   在其参考可见之前正确构建   符合指南“TSM03-J。不要部分发布   初始化对象“,第162页。

现在让他们打开他们已经提到过的第162页。这是另一个代码段。

class Foo {
    private Helper helper;

    public Helper getHelper() {
        return helper;
    }

    public void initialize() {
        helper = new Helper(42);
    }
}

public class Helper {
    private int n;

    public Helper(int n) {
        this.n = n;
    }

}

接着是它自己的解释:

  

如果一个线程在之前使用getHelper()方法访问帮助器   已经调用了initialize()方法,线程会观察到一个   未初始化的辅助领域。以后,如果一个线程调用initialize()   另一个调用getHelper(),第二个线程可能会观察其中一个   以下内容:

     
      
  • 帮助程序引用为NULL,
  •   
  • 完全初始化的Helper对象,n字段设置为42,
  •   
  • 部分初始化的Helper对象,未初始化的n包含默认值0。
  •   

所以,不要把我大胆的两个陈述相互矛盾?

A编写了一段代码来测试案例,运行了几次,从未得到过0.只有null或42.这是我的代码:

package com.sample;

public class Main {

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            Foo foo = new Foo();
            Initer initer = new Initer(foo);
            Getter getter = new Getter(foo);
            initer.start();
            getter.start();
        }
    }

    static class Getter extends Thread {

        private Foo foo;

        Getter(Foo foo) {
            this.foo = foo;
        }

        @Override
        public void run() {
            System.out.println(foo.getHelper());
        }
    }

    static class Initer extends Thread {

        private Foo foo;

        Initer(Foo foo) {
            this.foo = foo;
        }

        @Override
        public void run() {
            foo.initialize();
        }
    }

    static class Foo {
        private Helper helper;

        public Helper getHelper() {
            return helper;
        }

        public void initialize() {
            helper = new Helper(42);
        }
    }

    public static class Helper {
        private int n;

        public Helper(int n) {
            this.n = n;
        }

        @Override
        public String toString() {
            return Integer.toString(n);
        }
    }

}

2 个答案:

答案 0 :(得分:2)

这两个陈述并不矛盾。如果第一个示例,则字段nfinal。这就是Helper类不可变的原因。在第二个示例中,n不是final,Helper不是不可变的,这意味着可以返回部分初始化的实例。

就你的代码观察这一点而言,彼得是对的,在实践中很难进行测试有几个原因。

答案 1 :(得分:1)

除非字段为final,否则无法保证该值可见,

  • 无法保证不会赢得
  • 一旦代码预热,即10,000次迭代后,你更有可能看到这个问题。
  • 您正在使用println,它有自己的内存障碍,这会让您更难看到任何不一致。
相关问题