设计模式——单例模式

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

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

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

解决方法

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

相关推荐
X² 编程说33 分钟前
14.面试算法-字符串常见算法题(三)
java·数据结构·后端·算法·面试
请不要叫我菜鸡1 小时前
Go语言基础学习02-命令源码文件;库源码文件;类型推断;变量重声明
linux·后端·golang·类型推断·短变量·变量重声明·库源码文件
AskHarries2 小时前
Spring Boot集成Akka Cluster快速入门Demo
java·spring boot·后端·akka
少喝冰美式2 小时前
【大模型教程】如何在Spring Boot中无缝集成LangChain4j,玩转AI大模型!
人工智能·spring boot·后端·langchain·llm·ai大模型·计算机技术
cci2 小时前
Rust gRPC---Tonic教程
后端·rust·grpc
谢林v2 小时前
Explain的简单使用
后端
lucifer3112 小时前
深入理解 Spring Boot 自动配置原理
java·后端
岁岁岁平安2 小时前
《飞机大战游戏》实训项目(Java GUI实现)(设计模式)(简易)
后端·游戏·设计模式·飞机大战·java-gui
JavaGuide3 小时前
一条 SQL 语句在 MySQL 中是如何执行的?
java·数据库·后端·mysql
ABin-阿斌3 小时前
SpringBoot 整合 Easy_Trans 实现翻译的具体介绍
java·spring boot·后端