单例可以确保一个类只有一个实例,同时提供对此实例的全局访问
单例模式的好处:
- 避免实例对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约内存空间
- 避免由于操作多个实例导致的逻辑错误
- 起到了全局统一管理控制的作用
饿汉模式
1 2 3 4 5 6 7 8 9
| class EagerInitializedSingleton { private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
private EagerInitializedSingleton() {}
public static EagerInitializedSingleton getInstance() { return instance; } }
|
优点:
只 在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题
缺点:
单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了
适合单例占用内存比较小,在初始化时就会被用到的情况。但是,如果单例占用的内存比较大,或单例只是在某个特定场景下才会用到就不合适了
懒汉模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class LazyInitializationSingleton {
private static volatile LazyInitializationSingleton INSTANCE;
private LazyInitializationSingleton() {}
public static LazyInitializationSingleton getInstance() { if (INSTANCE == null) { synchronized (LazyInitializationSingleton.class) { if (INSTANCE == null) { INSTANCE = new LazyInitializationSingleton(); } } } return INSTANCE; } }
|
优点:
需要的时候才去初始化对象
缺点:
要注意线程安全问题
静态内部类
1 2 3 4 5 6 7 8 9 10 11 12
| class StaticInnerSingleton {
private StaticInnerSingleton() {}
public static StaticInnerSingleton getInstance() { return InnerSingleton.INSTANCE; }
public static class InnerSingleton { private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton(); } }
|
通过 类加载机制 保证只会创建一个实例
并且只要应用中不适用内部类,JVM 就不会去加载这个单例类,也就不会创建单例对象
这种方式可以很简单的实现懒汉模式
反射打破单例
上面的实现方式都可以通过反射来打破
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class ReflectionSingletonTest {
public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException { EagerInitializedSingleton instance1 = EagerInitializedSingleton.getInstance(); EagerInitializedSingleton instance2 = null; Constructor<?>[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors(); for (Constructor<?> constructor : constructors) { constructor.setAccessible(true); instance2 = (EagerInitializedSingleton) constructor.newInstance(); break; } System.out.println(instance1.hashCode()); System.out.println(instance2.hashCode()); } }
|
序列化打破单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class ReflectionSingletonTest {
public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException { EagerInitializedSingleton instance1 = EagerInitializedSingleton.getInstance(); EagerInitializedSingleton instance2 = null; Constructor<?>[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors(); for (Constructor<?> constructor : constructors) { constructor.setAccessible(true); instance2 = (EagerInitializedSingleton) constructor.newInstance(); break; } System.out.println(instance1.hashCode()); System.out.println(instance2.hashCode()); } }
|
反序列化的时候会生成一个新的对象
解决方法是提供一个 readResolve
方法
1 2 3
| protected Object readResolve() { return getInstance(); }
|
枚举
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class EnumSingleton {
private EnumSingleton() {}
public static EnumSingleton getInstance() { return instanceEnum.INSTANCE.instance; }
private enum instanceEnum { INSTANCE;
final EnumSingleton instance;
instanceEnum() { instance = new EnumSingleton(); } } }
|
通过枚举的方式来实现单例模式一直被誉为最佳方法
而且 反射和反序列化都无法打破枚举方式的单例
当反射通过 newInstance 创建对象时,会检查该类是否被 enum 修饰,如果是则抛出异常
序列化时,Java 仅仅是将枚举对象的 name 对象序列化,反序列化时这是通过 java.lang.Enum 的 valueOf 方法根据名字来查找枚举对象