Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/381.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 对象创建(状态初始化)和线程安全_Java_Inheritance_Concurrency - Fatal编程技术网

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

Java 对象创建(状态初始化)和线程安全,java,inheritance,concurrency,Java,Inheritance,Concurrency,我正在阅读《Java并发实践》一书,发现下面引用的语句真的很难令人相信(但不幸的是,它是有意义的) 我只是想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

我正在阅读《Java并发实践》一书,发现下面引用的语句真的很难令人相信(但不幸的是,它是有意义的)

我只是想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.");
      }
}
虽然在构造函数中设置的字段值似乎是第一个 写入这些字段的值,因此不存在“旧的” 要将值视为过时值,首先是对象构造函数 将默认值写入子类之前的所有字段 构造函数运行。因此可以看到默认值 将字段作为过时值

关于上述粗体陈述


我知道这种行为,但现在很清楚,构造函数的调用层次结构不能保证是原子的(在由锁保护的单个同步块中调用超级构造函数),但解决方案是什么?设想一个具有多个级别的类层次结构(即使不建议这样做,让我们假设它是可能的)。上面的代码snippest是我们每天在大多数项目中看到的一种原型。

我想理论上是可能的。这类似于双重检查锁定问题

public class Test {
    static Holder holder;

    static void test() {
        if (holder == null) {
            holder = new Holder(1);
        }
        holder.assertSanity();
    }
...
如果test()由2个线程调用,则线程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之后获取第二个值。它明确地说:

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

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

评论:

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

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


在对象完成构造并返回对象引用之前,对该对象的引用对另一个线程不可用(假定构造函数中没有生成引用)。

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

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

  • 引用在分配给另一个字段期间还没有值的字段

  • 在构造函数中引用一个值,该值直到以后在构造函数中才被赋值

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

  • 在Java 5之前,类似于:

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

    可能会造成麻烦,因为另一个线程可能会在MyClass构造函数完成之前获取对myObject的引用,并且会在对象内部看到错误的值(在本例中是0而不是10)。对于Java5,在构造函数完成之前,JVM不允许将myObject设置为非null

  • 今天,您仍然可以在构造函数中将myObject设置为
    this
    ,并完成相同的任务

如果您很聪明,还可以在初始化类字段之前获得它们


在您的代码示例中,
(n!=n)
如果两次读取
n
之间的值发生了变化,则该值为真。我想关键是
n
开始为零,get被构造函数设置为其他值,并且在构造过程中调用
assertSanity
。在这种情况下,
n
不是易变的,因此我认为不会触发断言。如果你把时间安排得恰到好处的话,它就会每百万次左右发生一次。在现实生活中,这种问题经常发生,足以造成严重破坏,但很少发生,以至于您无法重现它。

在Java中,多重继承是不可能的。在具有多重继承的语言中,有一些结构可以解决在两个或多个超类中具有相同字段的歧义。如果我们在讨论Java,我仍然不明白“保证是原子的”是什么意思?@M.Sameer编辑的措辞是正确的ragarding继承。什么意思是课堂上的“hirachy”,而不是“Level”上面的例子说明了什么?你是说你可能会从中摆脱断言错误吗?这对我来说很难相信。@JanZyka这本书就是这么说的,这句话是从已经在SO中讨论过的那本书中引用的:这也是真的吗?我是问,不是说不是。我理解线程可能会看到不同的对象或未初始化的对象。但我希望线程只看到单个值(默认值或通过构造函数设置),因此
n!=n
始终返回
false
。但是您的代码显示静态实例和方法,与问题中发布的代码不同。它仍然使用Holder实例和实例方法Holder。assertSanity()这是