一、单例模式
Demo demo = new Demo(); jvm发生了什么?
-
开辟空间
-
初始化控件
-
赋值 demo
指令重排可能会导致2.3的执行顺序改变。(对变量使用volatile关键字修饰,禁止指令重拍)
1. 懒加载
- 线程安全问题
- double check 加锁优化
- 编译器(JIT),CPU有可能对指令进行重排序,导致使用到尚未初始化的实例,可以通过添加volatile 关键字进行修饰,对volatile 修饰的字段,可以防止指令重排。
class LazySingleton {
/**
* volatile 可以禁止指令重排
*/
private static volatile LazySingleton instance;
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
// 字节码层面
// 1. 分配空间
// 2. 初始化对象
// 3. 赋值
}
}
}
return instance;
}
private LazySingleton(){}
}
2. 饿汉模式
只有在真正主动使用对应的类时, 才会触发初始化如(当前类是启动类即main函数所在类,直接进行new 操作,访问静态属性,方法,用反射访问类,初始化一个类的子类等。)
类加载的初始化阶段就完成了实例的初始化,本质上就是借助于jvm类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步的形式来完成类加载的整个过程)
类加载过程:
- 加载二进制数据到内存中,生成对应的Class数据结构
- 连接: a, 验证, b, 准备(给类的静态成员赋默认值),c, 解析
- 初始化:给类的静态变量赋初值
3. 静态内部类
- 本质上是利用类的加载机制来保证线程安全
- 只有在实际使用的时候,才会触发类的初始化,所以也懒加载的一种形式。
4. 反射攻击实例:
/**
* TODO
* @see java.lang.Runtime;
* @author kevin
* @version 1.0
* @date 2022/3/25
*/
public class HungrySingletonTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// Class<HungrySingleton> hungrySingletonClass = HungrySingleton.class;
System.out.println(HungrySingleton.name);
Constructor<HungrySingleton> declaredConstructor = HungrySingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
HungrySingleton hungrySingleton = declaredConstructor.newInstance();
HungrySingleton instance = HungrySingleton.getInstance();
System.out.println(hungrySingleton == instance); // false
}
}
class HungrySingleton{
public static String name = "name";
static {
System.out.println("hungrySingleton init");
}
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){
//if (instance != null) {
// throw new RuntimeException("singleton must be singleton.");
//}
}
public static HungrySingleton getInstance(){
return instance;
}
}
5. 枚举类型
- 天然不支持反射创建对应的实例,且有自己的反序列化机制
- 利用类加载机制保证线程安全
6. 序列化
1. 实现序列化接口java.io.Serializable
2. 定义静态常量serialVersionUID
3. 重写readResolve 方法
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
javap -v -p xxxxx.class