单例模式的五种实现方式

参考 https://blog.csdn.net/qq_42372017/article/details/121192445

1. 单例模式的四大原则

  1. 构造方法私有
  2. 以静态方法或者枚举返回实例
  3. 多线程环境下都是访问同一个实例
  4. 反序列化时不会重新构建对象

2. 单例模式通常有5种实现方法

2-1 饿汉模式

饿汉模式会在类初始化时就提前创建了对象,是一种以空间换取时间的方法,所以不存在线程安全问题。(记忆方法:一个人很饿,那他就会提前把食物准备好)

public class SingleTon {
    // 提前实例化
    private static SingleTon INSTANCE = new SingleTon();
    // 构造方法私有
    private SingleTon(){}
    
    public static SingleTon getInstance() {
        return INSTANCE;
    }
}

2-2 懒汉模式

懒汉模式会在方法被调用的时候才去创建对象,以时间换取空间,在多线程下存在线程安全问题,当多个线程调用getInstance()方法时可能都看到INSTANCE = null,从而导致多个线程去创建对象,违背单例模式的原则。

public class SingleTon {
    private static SingleTon INSTANCE = null;
    private SingleTon() {}

    public static SingleTon getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new SingleTon();
        }
        return INSTANCE;
    }
}

2-3 双重锁懒汉模式(DCL)

DCL模式是对懒汉模式的改进,第一次判断 INSTANCE == null是为了避免非必要加锁,只有在第一次加载的时候才会对Class对象进行加锁再实例化,这样既可以节约内存空间,又能保证线程安全。

public class SingleTon {
    private static SingleTon INSTANCE = null;
    private SingleTon() {}

    public static SingleTon getInstance() {
        if (INSTANCE == null) {
            synchronized (SingleTon.class) {
                if (INSTANCE == null) {
                    INSTANCE = new SingleTon();
                }
            }
        }
        return INSTANCE;
    }
}

但是因为 JVM 存在指令重排的功能,DCL也会存在线程不安全的情况。因为INSTANCE = new SingleTon();实例化对象看似只有一行代码,但其实在 JVM 中是分为三步执行的:

  1. 在堆内存中开辟内存空间;
  2. 在分配的内存空间中实例化SingleTon的各个参数;
  3. 将对象指向堆内存空间。

由于 JVM 的指令重排原因,所以有可能2还没执行就先执行了3,如果此时再被切换到线程B上,由于执行了3,INSTANCE != null已经非空了,会被直接拿出来用,这样的话,就会出现异常。这个就是著名的DCL失效问题。

具体解决方案是使用 volatile 关键字,可以防止指令重排,从而解决DCL失效问题:private volatile static SingleTon INSTANCE = null;

2-4 静态内部类模式(常用)

使用静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不用去初始化INSTANCE,故而不占内存。即当SingleTon第一次被加载时,并不需要去加载SingleTonHolder类,只有当获取INSTANCE实例,也就是getInstance()方法第一次被调用时,才会导致虚拟机加载SingleTonHolder类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
缺点:由于是静态内部类的形式去创建单例的,故外部无法传递参数进去,只能调用固定的构造函数。

public class SingleTon {
    private SingleTon() {}
    private static class SingleTonHolder {
        private static SingleTon INSTANCE = new SingleTon();
    }

    public static SingleTon getInstance() {
        return SingleTonHolder.INSTANCE;
    }
}

2-5 枚举模式(《Effective Java》推荐)

在《effective java》第83 条:慎用延迟初始化(这本书真的很棒)中说道,最佳的单例实现模式就是枚举模式。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。

public enum SingleTon {
    INSTACE;
    public void doSomething() {
        System.out.println("doSomething");
    }
}
// 调用方法:
class Main {
    public static void main(String[] args) {
        SingleTon.INSTACE.doSomething();
    }
}

posted @ 2023-09-12 15:35  yub4by  阅读(55)  评论(0编辑  收藏  举报