对象创建(状态初始化)和线程安全

时间:2013-10-04 11:18:46

标签: java inheritance concurrency

我正在研究“实践中的Java并发”这本书,发现在下面引用的陈述中很难相信(但不幸的是它有意义)。

http://www.informit.com/store/java-concurrency-in-practice-9780321349606

只是想明白这个100%

public class Holder {
    private int n;
    public Holder(int n) { this.n = n; }
    public void assertSanity() {
      if (n != n)
       throw new AssertionError("This statement is false.");
      }
}
  

虽然看起来构造函数中设置的字段值是第一个   写入这些字段的值,因此没有“较旧”   值作为过时值,首先是Object构造函数   将默认值写入子类之前的所有字段   构造函数运行。因此可以看到默认值   对于作为陈旧值的字段

关于上面的粗体陈述,

我知道行为但是现在很明显,这个构造函数的调用层次结构并不保证是ATOMIC(在锁定保护的单个同步块中调用超级构造函数),但是什么是解决方案?想象一个具有多个级别的类层次结构(即使不推荐,也可以假设它是可能的)。上面的代码片段是我们在大多数项目中每天都看到的一种原型。

4 个答案:

答案 0 :(得分:2)

你误读了这本书。它明确地说:

  

这里的问题不是Holder类本身,而是Holder没有正确发布。

所以上面的构造如果好的话。什么不好是将这样的对象不正确地发布到其他线程。这本书详细解释了这一点。

答案 1 :(得分:1)

创建新对象时,事情会顺序发生。我不知道精确的顺序,但它是这样的:分配空间并将其初始化为零,然后设置获取常量值的字段,然后设置获得计算值的字段,然后运行构造函数代码。当然,它必须在某处初始化子类。

因此,如果您尝试使用仍在构造的对象,则可以在字段中看到奇数,无效的值。这通常不会发生,但是要做到这一点:

  • 引用在分配给其他字段时尚未拥有值的字段。

  • 引用构造函数中的值,该值在构造函数中稍后才会被赋值。

  • 引用刚刚从ObjectInputStream读取的对象中的字段中的对象中的字段。 (OIS通常需要很长时间才能将值放在它所读取的对象中。)

  • 在Java 5之前,类似于:

    public volatile MyClass  myObject;
    ...
    myObject = new MyClass( 10 );
    

    可能会造成麻烦,因为另一个线程可以在MyClass构造函数完成之前获取对myObject的引用,并且它会在对象内部看到错误的值(在这种情况下为零而不是10)。对于Java 5,在构造函数完成之前,不允许JVM使myObject为非空。

  • 今天你仍然可以在构造函数中将myObject设置为this并完成同样的事情。

如果你很聪明,你也可以在初始化之前掌握Class字段。

在您的代码示例中,如果某些内容更改了(n != n)的两次读取之间的值,则n将为true。我想点{​​{1}}开始为零,构造函数将其设置为其他内容,并在构造期间调用n。在这种情况下,assertSanity不是易失性的,所以我认为断言不会被触发。让它变得不稳定,如果你准确地计算一切,它会每百万次左右发生一次。在现实生活中,这种问题经常发生,只会造成严重破坏,但很少,你无法重现它。

答案 2 :(得分:0)

我猜理论上有可能。它类似于双重检查锁定问题。

public class Test {
    static Holder holder;

    static void test() {
        if (holder == null) {
            holder = new Holder(1);
        }
        holder.assertSanity();
    }
...

如果2个线程调用test(),则线程2可能会在初始化仍在进行时看到持有者处于某种状态,因此n!= n可能恰好为真。这是n!= n的字节码:

ALOAD 0
GETFIELD x/Holder.n : I
ALOAD 0
GETFIELD x/Holder.n : I
IF_ICMPEQ L1

正如您所见,JVM将字段n加载到操作数堆栈两次。因此,第一个var可能会在init之前获得值,而在init之后获得第二个

答案 3 :(得分:0)

评论:

  

Object构造函数首先将默认值写入所有字段   在子类构造函数运行之前

似乎错了。我之前的经验是,在运行构造函数之前设置类的默认值。这是一个超级类,它会在构造函数运行之前看到它的初始化变量。这是朋友查看基类在构造过程中调用方法的bug的根,超类实现并在超类中将初始化定义的引用设置为null。该项将存在,直到进入构造函数,此时init将其设置为null值。

对象的引用不可用于另一个线程(假设在构造函数中没有生成),直到它完成构造并返回对象引用。