设计模式-单例模式

86 阅读2分钟

一、单例模式

Demo demo = new Demo(); jvm发生了什么?

  1. 开辟空间

  2. 初始化控件

  3. 赋值 demo

    指令重排可能会导致2.3的执行顺序改变。(对变量使用volatile关键字修饰,禁止指令重拍)

1. 懒加载

  1. 线程安全问题
  2. double check 加锁优化
  3. 编译器(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以同步的形式来完成类加载的整个过程)

类加载过程:

  1. 加载二进制数据到内存中,生成对应的Class数据结构
  2. 连接: a, 验证, b, 准备(给类的静态成员赋默认值),c, 解析
  3. 初始化:给类的静态变量赋初值

3. 静态内部类

  1. 本质上是利用类的加载机制来保证线程安全
  2. 只有在实际使用的时候,才会触发类的初始化,所以也懒加载的一种形式。

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. 枚举类型

  1. 天然不支持反射创建对应的实例,且有自己的反序列化机制
  2. 利用类加载机制保证线程安全

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