1:前言
- 单例模式:保证一个类只有一个实例,并且提供一个全局访问点
- 使用场景:重量级的对象,不需要多个实例,如线程池,数据库连接池
2:懒汉模式
延迟加载, 只有在真正使用的时候,才开始实例化。
- 存在的问题:
- 线程安全问题
- double check 加锁优化
- 编译器 (JIT) , CPU 有可能对指令进行重排序,导致使用到尚未初始化 的实例,可以通过添加volatile 关键字进行修饰, 对于volatile 修饰的字段,可以防止指令重排。
public class LazySingleton { // 禁止指令重排 private volatile static LazySingleton instance; private LazySingleton(){} public static LazySingleton getInstance(){ if(instance == null){ synchronized (LazySingleton.class){ if(instance == null){ instance = new LazySingleton(); // 字节码层 // JIT , CPU 有可能对如下指令进行重排序 // 1 .分配空间 // 2 .初始化 // 3 .引用赋值 // 如重排序后的结果为如 // 1 .分配空间 // 3 .引用赋值 如果在当前指令执行完,有其他线程来获取实例,将拿到尚未初始化好的实例 // 2 .初始化 } } } return instance; } }
2.1测试
public class LazySingletonTest { public static void main(String[] args) { Thread t1 = new Thread(()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); }); Thread t2 = new Thread(()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); }); t1.start(); t2.start(); } }
打印结果如下:
org.hzz.singleton.v1.LazySingleton@4a10f8eb org.hzz.singleton.v1.LazySingleton@4a10f8eb
3:饿汉模式
类加载的 初始化阶段就完成了 实例的初始化 。本质上就是借助于jvm 类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安 全(**JVM以同步的形式来完成类加载的整个过程**)。
3.1 类加载过程:
- 加载二进制数据到内存中, 生成对应的Class数据结构
- 连接: a. 验证, b.准备(给类的静态成员变量赋默认值),c.解析
- 初始化: 给类的静态变量赋初值
只有在真正使用对应的类时,才会触发初始化 如( 当前类是启动类即 main函数所在类,直接进行new 操作,访问静态属性、访问静态方 法,用反射访问类,初始化一个类的子类等.)
public class HungrySingleton { private final static HungrySingleton instance = new HungrySingleton(); public HungrySingleton(){ System.out.println("HungrySingleton 实例化"); } public static HungrySingleton getInstance(){ return instance; } }
3.2 测试
public class HungrySingletonTest { public static void main(String[] args) { HungrySingleton instance1 = HungrySingleton.getInstance(); HungrySingleton instance2 = HungrySingleton.getInstance(); System.out.println(instance2 == instance1); } } /** * true */
4:静态内部类
本质上是利用类的加载机制来保证线程安全
只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一 种形式。
public class InnerClassSingleton { private static class InnerClassHolder{ static { System.out.println("InnerClassHolder 初始化"); } private static InnerClassSingleton instance = new InnerClassSingleton(); } //构造器私有化 private InnerClassSingleton(){} public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } }
4.1 反射攻击
public class ReflectAtack { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Constructor<InnerClassSingleton> constructor = InnerClassSingleton.class.getDeclaredConstructor(); //越过构造器私有化 constructor.setAccessible(true); // 通过发射InnerClassHolder的static静态代码块没有执行 InnerClassSingleton innerClassSingleton = constructor.newInstance(); InnerClassSingleton instance = InnerClassSingleton.getInstance(); System.out.println(innerClassSingleton == instance); // false } }
4.2 静态内部类防止反射破坏
public class InnerClassSingleton { private static class InnerClassHolder{ static { System.out.println("InnerClassHolder 初始化"); } private static InnerClassSingleton instance = new InnerClassSingleton(); } private InnerClassSingleton(){ //当通过反射攻击走到这一步 if(InnerClassHolder.instance == null){ // 有意思 throw new RuntimeException("单例不允许多个实例"); } } public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } }
5:枚举类型
天然不支持反射创建对应的实例,且有自己的反序列化机制
利用类加载机制保证线程安全
public enum EnumSingleton { INSTANCE; public void print(){ System.out.println(this.hashCode()); } }