设计模式——单例模式

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

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

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

解决方法

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

相关推荐
Q_19284999061 小时前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
ZSYP-S1 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
Yuan_o_2 小时前
Linux 基本使用和程序部署
java·linux·运维·服务器·数据库·后端
程序员一诺2 小时前
【Python使用】嘿马python高级进阶全体系教程第10篇:静态Web服务器-返回固定页面数据,1. 开发自己的静态Web服务器【附代码文档】
后端·python
DT辰白3 小时前
如何解决基于 Redis 的网关鉴权导致的 RESTful API 拦截问题?
后端·微服务·架构
thatway19893 小时前
AI-SoC入门:15NPU介绍
后端
陶庵看雪3 小时前
Spring Boot注解总结大全【案例详解,一眼秒懂】
java·spring boot·后端
Q_19284999064 小时前
基于Spring Boot的图书管理系统
java·spring boot·后端
ss2734 小时前
基于Springboot + vue实现的汽车资讯网站
vue.js·spring boot·后端