从两种单例模式谈java类加载过程中静态变量的初始化问题

前言

之前的文章里有总结过java的单例怎么写,具体链接
java中的7种单例模式

经常听到,类的static变量在类加载时就会初始化,于是有了常说的两种单例模式的对比:饿汉式和静态内部类模式。通常的说法是,两种都是支持线程安全的(关于怎么个安全法请看我上面的链接),饿汉式不被推荐是因为会提前初始化,占用一部分内存。
那我们就用代码说话。

验证

根据jvm加载class文件的过程,先是讲class文件流加载到内存,然后验证是否符合class的文件结构,接着生成对应的Class对象丢到方法区。那么,当我们拿到一个类的Class对象时,说明类加载成功了。
Instance.java

public class Instance {
    private static Instance instance = new Instance();
    private Instance(){
        System.out.println("instance be alloc");
    }

    public static Instance get(){
        System.out.println("instance be called");
        return instance;
    }
}

入口main方法:

public static void main(String[] a){
        // 能拿到对应的class类说明已经加载到虚拟机中
        Class instanceClass = Instance.class;
        System.out.println("Instance load over"+instanceClass!=null);
    }

按照“类在加载后就会实例化static变量”的说法,那么“"instance be alloc”一定会打印出来。我们看下结果:
true
很遗憾,即使对象加载了,Instance对象也并没有被实例。接着调用Instance.get()方法:

public class Main {
    public static void main(String[] a){
        // 能拿到对应的class类说明已经加载到虚拟机中
        Class instanceClass = Instance.class;
        System.out.println("Instance load over"+instanceClass!=null);
        Instance.get();
    }
}

结果:
true
instance be alloc
instance be called
是的,当调用Instance.get()的时候,我们的Instance实例化了对象出来。但是请注意, System.out.println(“instance be called”);这行在return instance之前,说明调用时序是get()-》alloc()-》return

以上的过程,验证了类并不是在加载完之后就会实例static变量。那到底什么时候才会初始化呢?

1.当遇到new,getstatic,putstatic或者invokestatic这四条字节码指令的时候,如果该类没有进行
初始化,则需要先初始化.这四条指令对应的是实例化对象,获取一个静态变量,设置一个静态变量(常量
放在常量池中,不会触发),或者调用静态方法的时候.
2.当时候反射包的方法对类进行反射调用的时候
3.当初始化一个类的时候,发现该类的父类还没有进行初始化,则初始其父类
4.当jvm启动的时候,当用户指定执行一个主类(就是包含main的那个类),虚拟机会先初始化这个类.

很明显,上述的例子是因为满足第一条,执行static方法的时候编译器会生成invokestatic指令,这时候instance没有初始化,所以会执行Instance的构造方法,然后在return返回。

饿汉式和静态内部类式的区别

public class Instance1 {
    private static class Holder{
        private static Instance1 instance = new Instance1();
    }
    private Instance1(){
        System.out.println("instance1 alloc");
    }
    public static Instance1 get(){
        System.out.println("instance1 called");
        //特意不返回instance
        return null;
    }
}

注意,在get方法特意返回了null.

public class Main {
    public static void main(String[] a){
        // 能拿到对应的class类说明已经加载到虚拟机中
        Class instanceClass = Instance1.class;
        System.out.println("Instance load over"+instanceClass!=null);
        Instance1.get();
    }
}

执行结果:
true
instance1 called
发现Instance1的构造方法并没有被调用。让get()返回instance实例再打印:

public class Instance1 {
    private static class Holder{
        private static Instance1 instance = new Instance1();
    }
    private Instance1(){
        System.out.println("instance1 alloc");
    }
    public static Instance1 get(){
        System.out.println("instance1 called");
        return Holder.instance;
    }
}

结果是:
true
instance1 called
instance1 alloc
这下总算实例化了!但是!强调出来,顺序是get()-》print-》alloc()-》return
从上面讲到的初始化的时机来印证,当执行get()方法时,Instance类并没有静态变量,当执行Hoder.instance的时候,是触发了对instance静态变量的引用,编译时生成的invokestatic(内部类对外部类私有变量的引用,见之前的文章java 内类和外类的关系)触发,使得instance作为Holder的静态变量被初始化。

比较两个过程:
饿汉式:get-》alloc-》return
静态内部类:get-》print(或者其他的方法体)-》alloc-》return
发现后者是多了一个过程,那就是在get()和return之间的代码执行。

结论

首先,类加载的时候就会初始化静态变量,这是不准确的;其次静态内部类式确实比饿汉式晚实例化,但是仅限于get和return之间的代码块,如果这部分代码块很简单甚至没有,那么两者的差距微乎其微。
如果只是写一个简单单例,两者几乎不存在什么性能上的差异。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值