在实际编程中,我们可能有这样的需求:确保某个类在Java堆内存中只存在一个实例,这样的场景一般出现在该类实例作为控制器,或者在GUI编程中的视图窗口。
本文介绍单例模式的4中常用实现方式,并分析它们各自的优缺点:
1)、饿汉式
饿汉式实现简单,能够满足大部分应用场景,即使是在并发环境下也能够保证单例模式的正确性。
但是饿汉式也存在一些弊端:饿汉式的实例在类加载的初始化阶段就完成实例化,这也许过早了点,因为程序中若不使用或者在较晚阶段才会使用该实例的话,过早的实例化可能会造成资源不必要的浪费,系统启动时间也会有所影响。
2)、懒汉式
这是最原始的懒汉式,与饿汉式的区别在于,静态属性instance一开始并未实例化,因此在类加载阶段也就不会加载到内存中。其次,在getInstance静态方法中判断instance是否被实例化了,若未实例化则创建,否则直接返回,因此instance属性的实例化延迟到了运行期才完成,这很好的节省了系统的资源。
当然,这种原始的懒汉式也存在明显的弊端,若在并发环境下,getInstance静态方法可能会"同时"被多个线程访问,因此创建多个实例,这就背离了单例模式。因此需要对原始的懒汉式进行改进:
只需要在原来的基础上作两点改进:
1、使用volatile关键字修饰静态属性instance
2、对getInstance静态方法双重校验锁优化
优化后的懒汉式就可以满足并发环境的要求了,但是使用synchronize同步代码块可能会影响性能,可以使用ReentrantLock替代。
3)、静态内部类式
静态内部类式是一种比较推荐的方式,程序运行时调用getInstance静态方法时,内部类才会被加载到JVM内存中,因此实例化的过程也就被延迟到了运行期。再者,利用JVM类加载机制自身的特性(类构造器<cinit>只会被调用一次),使得即使在并发环境下也能保证只会创建一个实例。
4)、使用枚举
上述的4中实现单例模式的方式已经介绍完毕,如何去抉择还是应当考虑具体的情况,需要特别指出的是,这几中方式并不能真正100%的保障只存在一个实例,因为java语言中创建对象的方式可不只有new,通过反射一样可以进行实例化,因此在使用单例模式时还是需要尽量避免使用反射,下面提供一个例子,讲解如何使用反射机制破解单例模式:
Ending ...