单例模式说明
单例模式主要是让当前对象在程序中有且只有一个,一般情况下用于连接池等需要频繁调用而又不能影响内存的对象。下面将介绍各种单例的创建方式以及优缺点
饿汉式
说明:饿汉模式是在程序一开始就创建
/** * 饿汉模式 * @date 2021-07-13 22:59 */ public class SinglePatternCreate { private static SinglePatternCreate singlePatternCreate = new SinglePatternCreate(); public static SinglePatternCreate getInstance() { return singlePatternCreate;
} }
优点:简单方便,没有线程安全问题
缺点:这种方式在初始化类的时候就会装载对象,而不是在被调用时才装载,那么会造成内存浪费。
懒汉式
说明:懒汉模式是在是用对象时才创建对象
/** * 懒汉模式 */ public class SinglePatternCreate { private static SinglePatternCreate singlePatternCreate; public static SinglePatternCreate getInstance() { if (singlePatternCreate == null) { singlePatternCreate = new SinglePatternCreate(); } return singlePatternCreate; } }
优点:按需加载,不会造成空间浪费
缺点:线程不安全,在多线程情况下会创建出多个对象副本
保障懒汉单例线程安全-同步锁
说明:在方法上加syncronized修饰,让方法变成同步执行
/** * 懒汉模式-加锁保障安全 */ public class SinglePatternCreate { private static SinglePatternCreate singlePatternCreate; public synchronized static SinglePatternCreate getInstance() { if (singlePatternCreate == null) { singlePatternCreate = new SinglePatternCreate(); } return singlePatternCreate; } }
优点:保障线程安全性,只能获取到一个实例
缺点:加了同步锁效率上会降低很多
保障懒汉单例线程安全-双重校验
说明:在判断空对象时才使用synchronized关键字,并且在同步后创建之前在判断一次是否为空,防止重复创建
/** * 懒汉模式-双重校验 */ public class SinglePatternCreate { private static SinglePatternCreate singlePatternCreate; public static SinglePatternCreate getInstance() { if (singlePatternCreate == null) { // 当为空进入等待锁释放后创建对象 synchronized (SinglePatternCreate.class){ if (singlePatternCreate == null) { // 在创建对象前判断是否需要已有其他线程创建 singlePatternCreate = new SinglePatternCreate(); } } } return singlePatternCreate; } }
优点:性能相对直接加同步锁有所提升
缺点:会产生空指针。因为对象创建时,在底层是需要3步指令(1:分配内存、2:初始化赋值、3:引用指向堆)完成创建,在高频次调用情况下,可能会因为jvm或者cpu的性能优化,进行指令重排(正常顺序123执行,重排后132),那么会导致某一个线程在第一次判空时拿到的是已分配但未初始化的对象,直接返回给上一层调用,如果这样直接使用该对象,会产生空指针。因此需要下一步volatile
保障懒汉单例线程安全-volatile关键字
说明:volatile关键字,保障变量在线程之间的可见性和防止底层指令重排
/** * 懒汉模式-volatile */ public class SinglePatternCreate { private volatile static SinglePatternCreate singlePatternCreate; public static SinglePatternCreate getInstance() { if (singlePatternCreate == null) { synchronized (SinglePatternCreate.class) { if (singlePatternCreate == null) { singlePatternCreate = new SinglePatternCreate(); } } } return singlePatternCreate; } }
优点:兼容绝大部分情况
缺点:复杂度上升
IoDH实现单例(initialization Demand Holder)
说明:使用静态内部类创建饿汉单例模式,只有在调用静态内部类的时候,类加载才会初始化单例对象。类加载过程中是由jvm进行线程安全保障
/** * 单例模式 IoDH(initialization Demand Holder) */ public class SinglePattern { public SinglePattern() { System.out.println("3、构造方法初始化"); } private static class SinglePatternHolder { private static SinglePattern singlePattern = new SinglePattern(); static { System.out.println("4、单例对象持有者静态块"); } public SinglePatternHolder() { System.out.println("只有new的时候才会调用构造方法"); } } static { System.out.println("1、静态块初始化"); } public static SinglePattern getInstance() { System.out.println("2、获取实例"); return SinglePatternHolder.singlePattern; } public static void main(String[] args) throws NoSuchMethodException { SinglePattern.getInstance(); // 根据数据顺序初始化 // output: // 1、静态块初始化 // 2、获取实例 // 3、构造方法初始化 // 4、单例对象持有者静态块 } }
优点:去掉输出语句,代码简单,线程安全,并且只有在调用方法时才会被加载。
反射与单例
说明:通过反射创建对象是否与单例对象一致
/** * 单例模式 反射与单例 */ public class SinglePattern { private static class SinglePatternHolder { private static SinglePattern singlePattern = new SinglePattern(); } public static SinglePattern getInstance() { return SinglePatternHolder.singlePattern; } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor<SinglePattern> constructor = SinglePattern.class.getConstructor(); SinglePattern singlePattern = constructor.newInstance(); SinglePattern instance = SinglePattern.getInstance(); System.out.println(singlePattern == instance); // false } }
如果系统是使用反射创建对象,那么会产生多个对象,而单例将无意义。
反射与饿汉下多例处理
反射和饿汉下创建多个,可以在构造方法中抛出异常,保证只有一个对象
/** * 懒汉模式-多例限制创建 */ public class SinglePatternCreate { private volatile static SinglePatternCreate singlePatternCreate; public SinglePatternCreate() { if (SinglePatternCreate.singlePatternCreate != null) { throw new RuntimeException("this obejct already exists"); } } public static SinglePatternCreate getInstance() { if (singlePatternCreate == null) { synchronized (SinglePatternCreate.class) { if (singlePatternCreate == null) { singlePatternCreate = new SinglePatternCreate(); } } } return singlePatternCreate; } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { SinglePatternCreate instance = SinglePatternCreate.getInstance(); Constructor<SinglePatternCreate> constructor = SinglePatternCreate.class.getConstructor(); SinglePatternCreate singlePatternCreate = constructor.newInstance(); System.out.println(singlePatternCreate == instance); // Caused by: java.lang.RuntimeException: this obejct already exists } }
为什么先用反射在用方法获取实例可以输出false,但是先用方法获取实例再用反射会抛异常?
枚举单例
说明:枚举本身在编译后会成为继承enum类,而每一个枚举值都会成为一个内部静态成员变量,当只有一个枚举值,则可以当成单例模式使用,并且可以添加其它方法和成员变量,当成类来使用。
public enum SinglePatternEnum { SINGLE_PATTERN_ENUM; // 业务代码 public void businessMethods() { System.out.println("this is business process"); } }
public static void main(String[] args) { // 调用单例对象的方法 SinglePatternEnum.SINGLE_PATTERN_ENUM.businessMethods(); }
优点: 由jvm保证线程安全和单一实例,并且写法简单,在《effective java》说该方式为最佳单例模式。并且枚举不能被反射,不会因为反射产生多对象
序列化和反序列化之后的对象能否保持单例模式?
可以,前提是序列化的版本号一致,jvm就能通过字节码解析出相同对象。
总结:
这几种实现方式各有各的优缺点,比较推荐IoDH和枚举值。符合绝大多数情况。