一、单例
单例是一个常见的设计模式,常见有四种方式来实现,即懒汉式、饿汉式、枚举和静态内部类实现,这个模式的本质是为了控制内存中某个类的实例数量。
- 懒汉式采用懒加载,时间换空间,因此需要注意获取实例时的并发安全问题,即便正确并发,每次获取实例的时候还是要浪费一次判断;
- 饿汉式空间换时间,在定义单例对象时就完成实例化,因为JVM在初始化一个类的时候(即调用类构造函数<clinit>())会自动同步(饿汉式的单例对象必须用static修饰,否则会StackOverFlowError),因此不用关心线程安全问题,但是一旦完成类加载过程,无论是否使用该单例,该单例都已经实际占用内存;
- 枚举可以做天然的单例,枚举的思想本质就是该类的实例可以穷举,像季节、性别这种实例可以穷举的类型,然而枚举和饿汉式有一样的缺点,只要加载无论是否使用单例,都会占用内存,但是枚举的构造函数通过反射获取到以后再newInstance是非法的(见例一),因此枚举实现的单例相较之懒汉式和饿汉式,无需在私有的构造函数中再进行单例的判断从而控制构造函数被非法反射调用,即在私有构造函数中省略了if(instance != null){抛异常}。
至于单例类是否需要对反序列化进行控制的问题,一般单例类都是作为工具类来使用,不需要序列化,因此不需要实现java.io.Serializable接口;特殊情况下,如果单例类实现了序列化接口,只需要再readResolve方法中返回单例即可。
静态内部类实现单例,一是解决了懒加载线程安全问题(静态内部类实现单例,单利对象static修饰,所以不需要像懒加载在做null判断的时候进行同步,而是在类加载的第三个步骤初始化时由JVM自动同步)和获取单例时的判断问题;二是解决了饿汉式和枚举在加载时无论是否使用就分配内存的问题;三是可以和懒加载、饿汉式一样通过在私有构造中判断单例是否为null来进行非法构造方法反射的控制。
因此,静态内部类来实现单例,是相对较好的一种方式。需要提醒的是,本文是想深入讨论为什么性能好,在实际写项目的时候,大可不必吹毛求疵的追逐性能。
例一 枚举构造函数反射获取后调用newInstance非法
package cn.okc.demo;
public enum Gender {
MALE, FEMALE;
}
package cn.okc.demo;
import java.lang.reflect.Constructor;
public class TestGender {
public static void main(String[] args) throws Exception {
Class<Gender> clazz = Gender.class;
@SuppressWarnings("unchecked")
Constructor<Gender>[] constructors = (Constructor<Gender>[]) clazz.getDeclaredConstructors();
for (Constructor<Gender> c : constructors)
System.out.println(c);
Constructor<Gender> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
Gender gender = constructor.newInstance("MALE", 0);
System.out.println(gender);
}
}
private cn.okc.demo.Gender(java.lang.String,int)
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at cn.wxy.demo.TestGender.main(TestGender.java:15)
二、静态内部类实现单例及延迟加载验证测试
例二 静态内部类实现单例示例代码