1、单例模式
知识点:
1、模式定义:一个类只有一个实例,提供一个全局的访问点
应用场景:重量级别的对象,不需要多个实例,如线程池,数据库连接池等
类图分析:实例对象a 和 一个getInstance方法
2、字节码知识,字节码指令重排序
通过javap -v a.class 查看指令过程
创建对象(会涉及到指令排序),会有四个步骤
1 分配对象的内存空间
2 初始化对象
3 设置instance指向刚分配的内存地址
4 返回
指令顺序可能会经过JVM和CPU的优化,指令重排(2,3步骤可能会错乱导致会出现多个实例)
解决指令重排的方法,volatile关键字,会让JVM的顺序按照指定顺序执行
3、类加载机制
3.1、加载二进制数据到内存中,生成对应的Class数据结构
3.2、连接:a 验证(class是否符合jvm规范) b 准备(给类的静态变量赋值) c 解析
3.3、初始化:给类的静态变量赋初始值
真正使用类时,才会进行初始化
4、JVM序列化机制
静态内部类--需要实现readResolve()方法,才支持反序列化
实现serializable接口,并生成序列id(不生成的话,就会导致序列化和反序列化的对象不相同)
//序列化-该对象必须实现serializable
objectOutPutStream.writeObject(对象实例A)
//反序列化
A = objectOutPutStream.readObject();
默认情况下,序列化和反序列化两个对象是不相同的
要想一致,对象A必须要新增readResolve()方法(serializable接口中说明),返回单例对象A
枚举--本身就支持反序列化,不需要添加任何操作
5、单例模式在spring框架 和 JDK源码中的应用
6、五种实现方式
1.1、懒汉式,用到才会new对象----延迟加载,不安全(1、方法级别锁,影响性能,2、代码块锁)
static A a;
A static getInstance(){ if(A == null) { a = new A();}}
1.2、饿汉式,先new对象
1.3、上述都是线程不安全的-----双重检测机制-线程安全-懒加载-不能防止反射
A static getInstance(){
if(A == null) {
synchronized(A.class){
if(A == null) {
a = new A();
}
}
}
return a;
}
双重检测机制,以上还有一个隐藏漏洞
通过javap -v a.class 查看指令过程
创建对象涉及到指令重排
instance = new Singleton() 这行代码会被编译器译成如下JVM指令
memory = allocate() // 1 分配对象的内存空间
ctorInstance(memory) // 2 初始化对象
instance = memory // 3 设置instance指向刚分配的内存地址
指令顺序可能会经过JVM和CPU的优化,指令重排(2,3步骤可能会错乱)
memory = allocate() // 1 分配对象的内存空间
instance = memory // 3 设置instance指向刚分配的内存地址
ctorInstance(memory) // 2 初始化对象
这样就导致不为null时,对象还没初始化完成,这样会返回一个未初始化完成的对象
解决方案:需要在instance对象的前面增加一个volatile修饰词
volatile关键字,会让JVM的顺序按照指定顺序执行
1.4、静态内部类实现-线程安全-懒加载-不能防止反射
private static class Holder{
private static final Singleton INSTANCE = new Singleton()
}
private Singleton(){}
public static Singleton getInstance(){
return Holder.INSTANCE;
}
内部使用classloader来保持线程安全的
缺陷:可以通过反射,创建对个对象(1、获取构造器 2、设置构造器可用 3、newInstance方法构造对象)
如何阻止反射:
1、枚举
2、在默认构造方法中,再次判断实例是否为空,不为空就自定义异常,防止反射调用
1.5、枚举-线程安全-不是懒加载-能防止反射------------优先使用
public class Singleton {
public enum SingletonEnum{
INSTANCE;
private Singleton singleton;
private SingletonEnum(){
System.out.print("初始化单例")
singleton = new Singleton();
}
private Singleton getInstance(){
return singleton;
}
}
private Singleton(){}
public static Singleton getInstance(){
return SingletonEnum.INSTANCE.getInstance();
}
}