设计模式——单例模式

单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。

单例模式同时解决了两个问题, 所以违反了单一职责原则

  • 保证一个类只有一个实例
  • 为该实例提供一个全局访问节点

解决方法

  • 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符。
  • 新建一个静态构建方法作为构造函数。 该函数会 "偷偷" 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。

如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。 在下面的代码中大家可以看到是否符合上述的描述。

接下来下面将演示几种单例模式:

  • 懒汉模式(通俗解释:比较懒,等到调用的时候,在进行实例化对象)
  • 饿汉模式(通俗解释: 太饿了,想立马拥有,就在类加载的时候就完成了初始化)

懒汉模式

  • 这个类是懒汉模式,并且是线程不安全的
java 复制代码
public class Singleton {
    // 私有构造方法,防止外部类通过new Singleton()创建对象
    private Singleton(){}

    // 静态变量,懒加载,延迟初始化
    private static Singleton instance;

    // 静态方法,创建实例
    public static Singleton getInstance(){
        //多个线程同时调用,可能会创建多个对象
        if (instance == null){  // ①
            instance = new Singleton();
        }
        return instance;
    }
}

这时就有人就会问了,为什么是线程不安全呢?我看着很安全,嘎嘎安全啊。

那就让俺来解释解释吧!!!

当在多线程场景下,可能会有多个线程同时到达上述代码标记的①处,此时并没有实例化,所以就都符合条件,也就进行了创建多个对象了。下面有一段代码来演示。

java 复制代码
public class SingletonTest {
    public static void main(String[] args) {  
        for (int i = 0; i < 100; i++) { //如果没有测试出来,小伙伴们可以调大数量(100 -> 1000)
            new Thread(() -> {  
                Singleton instance1 = Singleton.getInstance();  
                Singleton instance2 = Singleton.getInstance();  
                if (instance1 != instance2) {  
                    System.out.println("线程不安全!创建了多个实例");  
                }  
            }).start();  
        }  
    }  
}
  • 懒汉模式,线程安全,但是效率不高(不推荐)
java 复制代码
public class Singleton {
    // 私有构造方法,防止外部类通过new Singleton()创建对象
    private Singleton(){}

    // 静态变量,懒加载,延迟初始化
    private static Singleton instance;

    // 静态方法,创建实例 添加了synchronized 升级为类锁
    public static synchronized Singleton getInstance(){
        //多个线程同时调用,可能会创建多个对象
        if (instance == null){ 
            instance = new Singleton();
        }
        return instance;
    }
}
  • 双重锁同步的懒汉模式(上述方式优化版)
java 复制代码
public class Singleton { 
    // 使用volatile修饰instance,保证多线程下的可见性,并且禁止指令重排序 
    private static volatile Singleton instance; 
    private Singleton() { 
    // 私有构造方法,防止外部类通过new Singleton()创建对象 
    } 
    public static Singleton getInstance() { 
        if (instance == null) { // 第一次检查实例是否存在,如果不存在才进入同步块 ①
            synchronized (Singleton.class) { 
                if (instance == null) { 
                    // 第二次检查实例是否存在,如果不存在才创建实例 
                    instance = new Singleton();   // ②
                } 
            } 
        } 
        return instance; 
    } 
}

注意: 在这个实现中,volatile关键字是关键。它确保所有线程都能看到instance变量的最新值,同时防止指令重排序,这对于确保双重检查锁定的正确性至关重要

那有的同学可能就要问了,为什么volatile关键字是关键呢?如果不加呢?此时就需要分析一波了。

当执行instance = new Singleton();这行代码时,JVM可能会将对象的初始化(分配内存空间、设置默认值等)和将对象引用赋值给instance的操作进行重排序 CPU会执行如下指令:

1.memory = allocate() 分配对象的内存空间

2.ctorInstance() 初始化对象

3.instance = memory 设置instance指向刚分配的内存

如果在单线程情况下按照上述的指令执行,那么确实不会出现问题,但是在多线程情况下,为了提高执行效率可能会打乱原有的执行顺序。

此时如果有两个线程(A,B),A到了①,B到了②此时如果出现了指令重排,按照了1,3,2执行,并且执行到了第三步骤,此时A判断不为null,就直接返回了该实例。但这时,instance还并没有完全初始化,就会出现问题。

饿汉模式

饿汉模式就比较简单一点了,没有上面的那么复杂。

饿汉模式,单例实例在类装载的时候进行创建,是线程安全的。

java 复制代码
 
public class SingletonExample2 {

    private SingletonExample2(){}

    private static SingletonExample2 instance = new SingletonExample2();

    public static SingletonExample2 getInstance(){
        return instance;
    }
}

好了,本文就介绍到这里了,后续的文章会陆陆续续的出,大家敬请期待哦~~~

提出一个小问题,大家可以在评论区讨论哦,来加深印象。

在spring中哪些使用到了单例模式呢?

相关推荐
小突突突1 小时前
Spring框架中的单例bean是线程安全的吗?
java·后端·spring
iso少年1 小时前
Go 语言并发编程核心与用法
开发语言·后端·golang
掘金码甲哥1 小时前
云原生算力平台的架构解读
后端
码事漫谈1 小时前
智谱AI从清华实验室到“全球大模型第一股”的六年征程
后端
码事漫谈1 小时前
现代软件开发中常用架构的系统梳理与实践指南
后端
Mr.Entropy2 小时前
JdbcTemplate 性能好,但 Hibernate 生产力高。 如何选择?
java·后端·hibernate
YDS8292 小时前
SpringCloud —— MQ的可靠性保障和延迟消息
后端·spring·spring cloud·rabbitmq
无限大62 小时前
为什么"区块链"不只是比特币?——从加密货币到分布式应用
后端
洛神么么哒2 小时前
freeswitch-初级-01-日志分割
后端
蝎子莱莱爱打怪3 小时前
我的2025年年终总结
java·后端·面试