首先是字节码文件生成。这里注意,在调用javap 查看字节码指令的时候,要指定-verbose参数,不然查看到信息不完全,只有类的基础信息。
一段如下的JAVA代码,public class TestP { private int n; public TestP(int n) { this.n = n; } private void ifP() { int n = 2; if (n >1){ TestP s = new TestP(1); System.out.println(s); } }}复制代码
编译后,如下:
看图中 IfP()方法的字节码部分。if_icmple复制代码
就是if语句的比较部分,紧接着下面的几条指令,
new invokespecial复制代码
两条指令,
new复制代码
是用来
创建一个对象, 并将其引用引用值压入栈顶
invokespecial #4复制代码
是用来
用于调用一些需要特殊处理的实例方法,包括实例初始化方法、 私有方法和父类方法。 #4处就是超类实例方法跟类实例方法, 后面的部分还有指令是用来 将对象引用赋值给变量的 所以,这里可以看出,一条 TestP p = new Testp(1);
有3部分指令共同完成。那JVM保证的是最终结果的正确性,并不会保证字节码的运行顺序,也就是说,很有可能不是按上述顺序运行字节码指令的,那如此的话,假如实例化初始方法的调用晚于synchronized关键字后,那就会造成异步的另一条线程在当前线程初始化单例后仍然进入第二个if 判null 的语法块内的情况,这样就错了。
所以,双重校验的单例模式,一定要用volitile修饰单例对象,以强制JVM字节码的顺序性。