总结分享 | 单例设计模式总结

单例设计模式

单例模式(Singleton Pattern)涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。


饿汉式

饿汉式就是在类加载的时候就进行初始化,基于classloader机制避免了多线程的同步问题。

这种方式比较常用,但容易产生垃圾对象,浪费内存。

public class Singleton1 { 
    private static final Singleton1 instance = new Singleton1(); 
    
    private Singleton1 (){} 
    
    public static Singleton1 getInstance() {  
    	return instance;  
    }  
}

但是,该单例代码能受到反射的破坏;如果该类实现了Serializable接口,还可以通过反序列化重写readResolve()方法来破环单例。

public class Singleton1 implements Serializable {
    private Singleton1() {
        if (INSTANCE != null) {
            throw new RuntimeException("单例对象不能重复创建");
        }
        System.out.println("private Singleton1()");
    }

    private static final Singleton1 INSTANCE = new Singleton1();

    public static Singleton1 getInstance() {
        return INSTANCE;
    }

    public Object readResolve() {
        return INSTANCE;
    }
}
  • 构造方法抛出异常是防止反射破坏单例
  • readResolve() 是防止反序列化破坏单例

但是无法防止 unsafe 破环单例


枚举饿汉式

这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

public enum Singleton2 {
    INSTANCE;

    public static Singleton2 getInstance() {
        return INSTANCE;
    }

}

枚举饿汉式能天然防止反射、反序列化破坏单例


懒汉式

懒汉式单例的主要特点就是被外部类调用的时候内才会创建实例。

1、不加锁(非线程安全)

public class Singleton3 {
    private Singleton3() {}

    private static Singleton3 INSTANCE = null;

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

2、方法加锁(线程安全)

public class Singleton3 {
    private Singleton3() {}

    private static Singleton3 INSTANCE = null;

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

}

实际上,只有第一次创建单例对象的时候需要加同步锁,后续掉用反而会因为同步锁而阻塞,导致程序性能下降。

3、双重校验锁(DCL)

这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

public class Singleton4 {
    private Singleton4() {}

    private static volatile Singleton4 INSTANCE = null; // 可见性,有序性

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

1、为何必须加 volatile:

  • INSTANCE = new Singleton4() 不是原子的,分成 3 步:创建对象、调用构造、给静态变量赋值,其中后两步可能被指令重排序优化,变成先赋值、再调用构造
  • 如果线程1 先执行了赋值,线程2 执行到第一个 INSTANCE == null 时发现 INSTANCE 已经不为 null,此时就会返回一个未完全构造的对象

4、静态内部类

这种方式能达到双检锁方式一样的功效,但实现更简单。

对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。

这种方式只适用于 静态域 的情况,双检锁方式可在 实例域需要延迟初始化时 使用。

public class Singleton5 {
    private Singleton5() {}

    private static class Holder {
        static Singleton5 INSTANCE = new Singleton5();
    }

    public static Singleton5 getInstance() {
        return Holder.INSTANCE;
    }
}

内部类是在方法调用之前初始化的,巧妙的避免了线程安全问题。

静态内部类的写法既没有饿汉式的内存浪费问题,也没有加锁带来的性能开销,避免了双检锁的缺点。


JDK 中单例的体现

  • Runtime 体现了饿汉式单例模式

  • System中的Console 体现了双检锁懒汉式单例

  • Collections 中的 EmptyNavigableSet 内部类懒汉式单例
  • ReverseComparator.REVERSE_ORDER 内部类懒汉式单例
  • Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例
posted @   Azureblue"  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示