设计模式第1讲——单例模式(Singleton)

单例模式是一种创建型设计模式,确保一个类只有一个实例并提供全局访问。它有全局唯一实例、节省资源和避免共享资源竞争等优点,但也存在不支持多态和降低代码灵活性的缺点。常见的实现方式包括饿汉式、懒汉式(推荐双重校验锁)、静态内部类和枚举。在实际应用中,应根据性能需求选择合适的单例实现策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、什么是单例模式

单例模式属于创建型设计模式,是Java种最简单的设计模式之一。

这种模式要求一个类仅有一个实例,并且提供了一个全局的访问点。在程序中多次使用同一个对象且作用相同时,所有需要调用的地方就会共享这个访问点(对象),从而防止因频繁创建对象而损耗系统性能。

二、优缺点

优点:

  • 全局唯一实例:通过单例模式,可以确保一个类只有一个实例对象存在,全局范围内可以方便地访问该实例。
  • 节省资源:由于只有一个实例存在,单例模式可以节省系统资源(如内存、CPU等),避免多次创建和销毁对象的开销。
  • 避免对共享资源的竞争:在多线程环境中,单例模式可以避免对共享资源的竞争问题,确保数据一致性和线程安全性。

缺点:

  • 不支持多态:单例模式一般只能创建一个固定类型的对象实例,不支持多态的灵活性。
  • 降低了代码的灵活性:对于使用了单例模式的代码,如果需要改变实例化策略或使用其他类型的实例,可能需要修改代码和重新设计。
  • 隐藏了依赖关系:单例模式可能隐藏了对其他组件或对象的依赖关系,使得代码的结构不够清晰,增加了代码的理解和维护的难度。

三、应用场景

3.1 生活场景

  • 一个班级只有一个班主任。
  • windows桌面上的回收站:我们打开一个回收站,当我们再次试图打开一个新的时,Windows并不会为你弹出一个新窗口,也就是说整个系统运行过程中,系统只维护一个回收站的实例。
  • 网站计数器:一般也采用单例模式实现,如果你存在多个计数器,每一个用户访问都刷新的计数器,这样的话你的实际数量是难以同步的。

3.2 Java场景

Bean定义的默认作用域:在Spring中,默认情况下,所有的Bean都是单例的,也就是说 Spring 容器中只会创建一个特定类型的Bean实例。

Spring容器(ApplicationContext):Spring容器本身也是一个使用了单例模式的对象。无论是基于XML配置的ClassPathXmlApplicationContext,还是基于注解的AnnotationConfigApplicationContext,它们都是单例的,只会生成一个容器实例。

Spring AOP中的切面(Aspect):在Spring AOP中,切面是用来定义横切关注点(如日志、事务等)的类。默认情况下,Spring会将切面定义为单例,以确保在整个应用程序中只有一个切面实例,以避免创建过多的代理对象。

四、创建单例

注意看代码中的注释。

4.0 代码结构 

4.1 饿汉式(2种)

/**
 * 饿汉:在类刚一初始化的时候就立即把单例对象创建出来,下面两种都是饿汉模式的实现
 */
public class Singleton {

    private Singleton() {}

    private static Singleton instance = new Singleton();
    public static Singleton getInstance() {
        return instance;
    }


    private static Singleton instance1=null;
    static {
        instance1=new Singleton();
    }
    public Singleton getInstance1(){
        return instance1;
    }

}

4.2 懒汉式 (4种)

/**
 * 懒汉:懒加载,就是在需要的时候才回去创建对象
 */
public class Singleton {

    private Singleton() {
    }

    /**
     * 1.单例模式【线程不安全,不推荐】
     * 因为没有加锁synchronized,严格意义上不算单例。
     * @return
     */
    private static Singleton instance1;

    public static Singleton getInstance() {
        if (instance1 == null) {//这里是不安全的,可能得到两个不同的实例
            instance1 = new Singleton();
        }
        return instance1;
    }


    /**
     * 2.线程安全但效率低【不推荐】
     * 99%的情况下不需要同步
     * @return
     */
    private static Singleton instance2;

    public static synchronized Singleton getInstance1() {
        if (instance2 == null) {
            instance2 = new Singleton();
        }
        return instance2;
    }

    /**
     * 3.单例模式,线程不安全【不推荐】
     * 虽然加了锁,但是等到第一个线程执行完instance2=new Singleton();跳出锁时
     * 令一个线程恰好刚判断完instance2为null,此时又会加载另一个实例
     */
    private static Singleton instance3;

    public static Singleton getInstance2() {
        if (instance3 == null) {
            synchronized (Singleton.class) {//不安全
                instance3 = new Singleton();
            }
        }
        return instance3;
    }

    /**
     * 4.双重校验锁:延迟加载+线程安全【推荐】
     */
    private static volatile Singleton instance4;

    public static Singleton getInstance4() {
        if (instance4 == null) {
            synchronized (Singleton.class) {
                if (instance4 == null) {
                    instance4 = new Singleton();
                }
            }
        }
        return instance4;
    }
}

4.3 静态内部类

/**
 * 静态内部类【推荐】
 * 这种方式跟饿汉式方式采用的机制类似,但又有不同。
 * 两者都是采用了类装载的机制来保证初始化实例时只有一个线程。
 * 不同的地方:
 * 在饿汉式方式是只要Singleton类被装载就会实例化,
 * 内部类是在需要实例化时,调用getInstance方法,才会装载SingletonHolder类
 * 优点:避免了线程不安全,延迟加载,效率高。
 */
public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton() {
    }

    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

4.4 枚举

/**
 * 枚举实现
 * 这种方式还没有被广泛采用,但是这种是实现单例的最佳方式。
 * 线程安全,自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化
 */
public enum Singleton {
    INSTANCE;
    public void whateverMethod(){
        System.out.println("单例模式实现的最佳方式");
    }
}

五、总结

  1. 单例最常见的写法:懒汉式、饿汉式。
  2. 懒汉式:懒加载,在调用的时候才会实例化对象,推荐使用双重校验锁的方式。
  3. 饿汉式:类加载的时候就已经实例化好对象了,不会存在并发安全和性能问题。
  4. 在开发中,如果内存要求高,就用懒汉式,不高则用饿汉式。
  5. 最优雅的方式是使用枚举,代码极简,没有线程安全问题,又能防止反射和序列化时破坏单例。

END:更多设计模式的介绍,推荐移步至👉 23种设计模式学习导航(Java完整版)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橡 皮 人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值