单例模式是指某个类只能被实例化一次,用来表示全局或系统范围的组件,常用于日志记录,工厂,平台组件管理等。单例模式看似简单实际很难。
单例本身有多种实现方法,总体上可分为懒汉模式和饿汉模式两种,懒汉模式相对简单
public class FooSingleton { public final static FooSingleton INSTANCE = new FooSingleton(); private FooSingleton() { } public void bar() { } }
私有构造函数在楼初始化时调用且只调用一次,JVM保证在类完整初始化之后才会被其它多个线程调用。
饿汉模式常见线程安全的实现方式有三种:
- 双重检测加锁模式,注意Java中new FooSingleton()操作实际是有分配实例内存、引用指向内存地址,初始化实例3个原子操作组成。除了双重加锁外,必须要将实例instance定义为volatile类型防止指令重排才能保证完全正确。
- 静态内部类方法,内部类只有在它的静态方法、变量等被调用时才加载。
public class FooSingleton4 { private FooSingleton4() { } public static FooSingleton4 getInstance() { return FooSingleton4Holder.INSTANCE; } private static class FooSingleton4Holder { private static final FooSingleton4 INSTANCE = new FooSingleton4(); } }
- 使用枚举,根据Java语言规范8.9,“Enum的final克隆方法保证枚举永远无法被克隆,其特殊的序列化机制保证无法反序列化得到拷贝的对象。同时,还禁止利用反射对枚举进行实例化。保证了这四个方面,在枚举常量之外,就不会有其他同类的枚举实例存在。”
public enum FooEnumSingleton { INSTANCE; public static FooEnumSingleton getInstance() { return INSTANCE; } public void bar() { } }
要想真正实现单例,防止出现多个实例,还要考虑无法单例无法通过clone方法被克隆,无法通过序列化反序列化被拷贝,无法通过反射被重新序列化等。现在一般多推荐采用单例的写法,不仅代码简洁,相对来说也能更好的预防上述问题。