设计模式——单例模式

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

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

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

解决方法

  • 将默认构造函数设为私有, 防止其他对象使用单例类的 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中哪些使用到了单例模式呢?

相关推荐
devlei7 小时前
从源码泄露看AI Agent未来:深度对比Claude Code原生实现与OpenClaw开源方案
android·前端·后端
努力的小郑8 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
Victor3569 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3569 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁9 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp9 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
宁瑶琴11 小时前
COBOL语言的云计算
开发语言·后端·golang
普通网友11 小时前
阿里云国际版服务器,真的是学生党的性价比之选吗?
后端·python·阿里云·flask·云计算
IT_陈寒12 小时前
Vue的这个响应式问题,坑了我整整两小时
前端·人工智能·后端
Soofjan13 小时前
Go 内存回收-GC 源码1-触发与阶段
后端