1、饿汉式

说明:

 汉式单例模式在类加载时就创建实例,因此线程安全,但可能会造成资源浪费。如果实例一直未被使用,就会造成不必要的内存占用。

优点:

  1. 线程安全(JVM在类加载时初始化INSTANCE)。
  2. 结构简单,访问速度快。

缺点:

 不支持懒汉式加载,如果INSTANCE长时间未使用,就会占用内存。

使用场景​:

 实例体积较小,程序启动时一定会用到的。

代码示例:

public class Hungry {

    private static final Hungry INSTANCE = new Hungry();


    private Hungry() {
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    public static Hungry getInstance() {
        return INSTANCE;
    }

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        for (int i = 0; i < 5; i++) {
            System.out.println(Hungry.getInstance());;
        }
    }
}

2、懒汉式

说明:

 指实例需要时进行创建,而不是在类加载时就进行创建。

优点:

 静态内部类方式结合懒汉式加载和线程安全,不会造成性能损耗。

使用场景:

 实例对象体积较大时,使用不确定,推荐懒汉式(静态内部类)或者枚举。

代码示例:

public class Lazy {

    private Lazy() {
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    private volatile static Lazy lazy;

    //双重检测模式的-懒汉单例-dcl
    public static Lazy getInstance() {
        if (lazy == null) {
            synchronized (Lazy.class) {
                if (lazy == null) {
                    lazy = new Lazy();
                    /**
                     * new Lazy()不是原子性操作。
                     * <p/>
                     * 1、分配内存空间。
                     * 2、执行构造方法、初始化对象。
                     * 3、把这个对象指向这个空间。
                     *<p/>
                     *在多线程情况下,由于上面三个步骤可能会出现132,导致其他线程空指针的情况,所以需要volatile 防止指令重排。
                     */
                }
            }
        }

        return lazy;
    }


    /**
     * @param args
     */
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Lazy lazy1 = Lazy.getInstance();
        Lazy lazy2 = Lazy.getInstance();
        System.out.println(lazy1);
        System.out.println(lazy2);

    }
}

3、静态内部类

说明:

 结合懒加载和线程安全的有点,被认为最优的单例实现方式之一

代码示例:

public class Holder {

    public Holder() {

    }
    
    public static Holder getInstance() {
        return InnerClass.HOLDER;
    }

    public static class InnerClass {
        private static final Holder HOLDER = new Holder();
    }

}

原理解析:

  1. InnerClass 不会在类加载时加载,只会在调用getInstance后加载。
  2. HOLDER 变量在 InnerClass 类加载时初始化,而JVM 保证了类的加载过程是线程安全的,因此不会有并发问题。
  3. 由于 SingletonHolder 只有在 getInstance() 方法被调用时才加载,因此实现了懒加载,不会浪费内存。
  4. 和 synchronized 方式相比,没有额外的同步开销,访问速度更快。

4、枚举

说明:

 是最安全、最简洁、最推荐的单例实现方式。它不仅​天然线程安全​,还能防止​反射攻击和序列化破坏​。

示例代码:

public enum EnumSingle {

    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        EnumSingle instance = EnumSingle.INSTANCE;
        EnumSingle instance1 = EnumSingle.INSTANCE;
        System.out.println(instance);
        System.out.println(instance1);
    }
}

原理解析:

  1. JVM 保证线程安全

  类型的实例是​由 JVM 保证只加载一次​,不会出现多线程并发问题。

  1. 防止反射攻击

 反射可以破坏普通单例模式(如 private constructor),但​反射无法创建枚举的实例​,因为 Enum类的newInstance()方法会抛出**。

  1. 防止序列化攻击
  • 普通单例类 可能在反序列化时创建新实例,需要手动实现 readResolve() 方法来防止。
  • 枚举单例 天然支持序列化,JVM 保证反序列化时不会创建新实例。

5、扩展知识点(反射破坏单例)

说明:
除枚举以外,反射的存在所有单例都是不安全的​。

示例代码:

public class Lazy_Reflect {

    private static boolean iga = false; //红绿灯字段

    private Lazy_Reflect() {
        synchronized (Lazy_Reflect.class) {        //三层锁机制。
            if (iga == false) {
                iga = true;
            } else {
                throw new RuntimeException("不要试图使用反射破坏异常!");
            }
        }
    }

    private volatile static Lazy_Reflect lazy;

    //双重检测模式的-懒汉单例-dcl
    public static Lazy_Reflect getInstance() {
        if (lazy == null) {
            synchronized (Lazy_Reflect.class) {
                if (lazy == null) {
                    lazy = new Lazy_Reflect();
                    /**
                     * new Lazy()不是原子性操作。
                     * <p/>
                     * 1、分配内存空间。
                     * 2、执行构造方法、初始化对象。
                     * 3、把这个对象指向这个空间。
                     *<p/>
                     *在多线程情况下,由于上面三个步骤可能会出现132,导致其他线程空指针的情况,所以需要volatile 防止指令重排。
                     */
                }
            }
        }

        return lazy;
    }


    /**
     * 反射的存在所有单例都是不安全的,除枚举以外。
     *
     * @param args
     */
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {

        //反射,获取构造器
        Field iga1 = Lazy_Reflect.class.getDeclaredField("iga");
        iga1.setAccessible(false);

        Constructor<Lazy_Reflect> declaredConstructor = Lazy_Reflect.class
                .getDeclaredConstructor(null);
        declaredConstructor.setAccessible(false);
        Lazy_Reflect lazy2 = declaredConstructor.newInstance();

        iga1.set(lazy2,false);

        Lazy_Reflect lazy1 = Lazy_Reflect.getInstance();

        System.out.println(lazy1);
        System.out.println(lazy2);

    }
}
最后修改:2025 年 02 月 21 日
如果觉得我的文章对你有用,请随意赞赏