我刚刚读过 http://www.javaworld.com/javaworld/jw-04-2003/jw-0425-designpatterns.html?page=5 它说:
编译器可以自由地给单例成员变量赋值 在单例的构造函数之前 叫
我想知道这是不是一个错字。他们真的想说:JVM 的实现是自由的,而不是编译器是自由的。
我的第二个问题是 C#/VB 也有这个问题吗? (其中“编译器”可以自由地为变量赋值,甚至在变量完全启动之前/甚至在变量类的构造函数完全运行之前。
在Java中,为对象分配内存和调用构造函数是两个独立的操作。例如,类似
Object o = new Object();
编译成这些字节码:
0: new #2; //class java/lang/Object
3: dup
4: invokespecial #1; //Method java/lang/Object."<init>":()V
7: astore_1
指令 0 之后,对已分配但未构造的对象的引用位于堆栈上。直到偏移量 4 才会调用构造函数。绝对没有什么可以阻止编译器将该引用分配给它想要的任何变量,包括静态成员。因此这篇文章是正确的。
我不知道 CLR 字节码,但我想它与 JVM 的指令集相当接近,而且我猜想该运行时也存在同样类型的与线程相关的警告。它当然适用于本机代码编译器。
问题第一部分的答案是你是正确的,尽管这更多的是一个草率的术语而不是拼写错误或错误。 (简单地说,编译器不会为变量赋值……这只在编译器生成的代码被执行时才会发生。)
技术上更准确的重述是:
“...因为编译器可以自由生成代码,可能导致在调用单例构造函数之前将单例成员变量的值写入内存,并且构造的对象已刷新到内存。”
这种事情最有可能发生在本机代码编译器级别,当编译器(合法地)重新排序指令时,或者仅仅是内存流水线的结果。 Java 内存模型特别允许这样做,以便编译器能够生成在多核计算机上运行速度更快的代码。 (另一方面,您的多线程代码必须以所需的方式同步,否则它可能不可靠。)
(理论上字节码编译器也可以重新排序字节码,但很可能不会。字节码编译器进行细粒度优化没有什么价值。事实上,它可能是有害的,因为它可能会导致对于 JIT 编译器的优化器来说更困难。)
我会将 C# 和 VB 案例留给熟悉这些语言规范的人。