知识点
1、模式定义/应用场景/
2、字节码的试试和指令重排序
3、类加载机制
4、序列化机制
5、单例模式在spring框架源码中的应用
一、模式适用的场景
保证一个类只有一个实例,并且提供一个全局的访问点。
适用于: 重量级的对象,不需要多个实例,比如线程池、连接池等
二、五种单例模式和序列化机制
2.1 懒汉模式:延迟加载,只有在真正适用的时候,才实例化对象
知识点:线程安全问题 1、double-check加锁优化 2、JIT编译优化,指令重排
class LazySingleton{
private volatile static LazySingleton instance;
private LazySinglton(){}
public static LazySingleton getInstance(){
if(instance == null){
synchronized(LazySingleton.class){
// 如果只是加锁,不做第二道判断的话,其他线程进入第一道判断之后,还是拿到锁之后,仍然会实例化对象。
if(instance == null){
instance = new LazySingleton();
}
}
}
return instance;
}
}
为什么需要加上volatile关键字? JVM在实例化对象的时候,可以分为三个部分
1、分配对象的空间
2、实例化对象
3、给引用赋值
由于第二步和第三步没有数据依赖,所以编译器可能会对他们进行重排序,如果重排序后的结果如下
1、分配对象的空间
2、给引用赋值
3、实例化对象
那么线程就有可能拿到没有初始化好的对象。
2.2饿汉模式
在类加载的初始化阶段的时候,就完成了实例的初始化,本质就是借助JVM的类加载机制,保证实例的唯一性(初始化的过程只会执行一次)及线程安全(JVM以同步的方式来完成类加载的整个过程)。
类加载的过程: 1、加载二进制数据到内存中,生成对应的Class数据结构 2、链接:a、验证 b、准备(给类的静态成员变量赋默认值) c、解析 3、初始化:给类的静态变量赋初值
只有真正适用对应的类时,才会触发初始化,如
- 当前类时启动类(main函数所在类)
- 直接进行new操作
- 访问静态属性
- 访问静态方法
- 用反射访问类
- 初始化一个类的子类等
class HungrySingleton{
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}
2.3静态内部类
1、本质上是利用类的加载机制来保证线程安全
2、只有实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式。
class InnerClassSingleton{
private static class InnerClassHoder{
private static InnerClassSingleton instance = new InnerClassSingleton();
private InnerClassSingleton(){};
public static InnerClassSingleton getInstance(){
return InnerClassHoder.instance;
}
}
}
2.4使用反射破坏单例
Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();
InnerClassSingleton instance = InnerClassSingleton.getInstance();
System.out.println(innerClassSingleton == instance); //false
静态内部类可以防止反射破坏
class InnerClassSingleton{
private static class InnerClassHoder{
private static InnerClassSingleton instance = new InnerClassSingleton();
private InnerClassSingleton(){
if(InnerClassHoder.instance != null){
throw new RuntimeException("单例不允许有多个实例");
}
};
public static InnerClassSingleton getInstance(){
return InnerClassHoder.instance;
}
}
2.5枚举类型
1、newInstancede 方法中已经对枚举类型进行判断,无法重复实例化。所以枚举类型天然不支持反射对应的实例,并且有自己的反序列机制。
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
2、利用类加载机制保证了线程安全。
2.6序列化
首先我们可以思考一下,我们创建的一个对象,和通过rpc调用(进行了序列化和反序列)之后的对象还是同一个对象吗?
答案是否定的。
我们需要在序列化的时候,实现readResolve方法
Object readResolve() throws ObjectStreamException{
return InnerClassHolder.instance;
}
Notes: 需要自定义序列化Id,否则会跑一个序列化Id不一致的异常。