单例模式底层避免线程安全问题的设计思想

单例模式(Singleton Pattern)是一种常用的软件设计模式,它确保一个类只有一个实例,并提供一个全局访问点。这种模式在需要全局状态或者希望整个应用程序中某个对象只有一个实例时非常有用。

单例模式的关键特点包括:

  1. 唯一实例:单例类通过私有构造函数和静态实例变量确保只有一个实例被创建。
  2. 全局访问点:单例类提供一个公共的静态方法,允许其他对象获取到这个唯一实例的引用。
  3. 延迟初始化:单例实例可以在第一次使用时创建,也可以在类加载时立即创建。延迟初始化有助于节省资源,因为它允许系统在需要时才创建实例。
  4. 线程安全:在多线程环境中,单例模式需要确保线程安全,避免多个线程同时创建多个实例。

单例模式的几种实现方式:

1.饿汉模式

复制代码
public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton() {}

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

这种实现方式会在类加载的过程中就创建唯一实例,并**不存在线程安全问题。**但是,如果实例从未被使用,这种方式会造成资源浪费,而且当我们的对象比较大时,这种方式会在一定程度上影响我们类的启动速度。

2.懒汉模式

复制代码
public class LazySingleton {
    private static volatile LazySingleton instance;

    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized (LazySingleton.class) {
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

这种模式为什么要使用双重检测锁,接下来我们来看看原因:

首先在只加一层判断,不加锁时,很明显也存在线程安全的问题,如图

出现了线程安全问题,我们很容易想到使用synz上锁,对我们的方法上锁,确实这样可以避免了我们的线程安全问题,但是锁的力度太大了,我们平常在开发过程中其实还会有其他的业务代码,而这些代码并不是锁要竞争的资源,所以我们应该锁临界资源=多线程竞争的资源。

好,那我们对竞争资源上锁,咦,这是我们有出现线程安全问题了,如图,我们线程1和线程2同时通过了判断1,此时假设线程1抢到了锁,进行实例化,结束流程,释放锁资源,线程2拿到锁,同样进行下面的流程,进行实例化,这些尴尬了,回到了最开始的问题,那怎么办呢?

其实只要在加一层判断就完事了!我们可以在脑子里过一遍流程,这样一来既可以解决了线程安全的问题也可以比避免我们锁的力度太大的问题。

这里在介绍一下volatile,在我们的源码中其实加上了volatile关键词,目的是避免指令重排,保证我们变量的可见性。

什么是指令重排?

我们的单例模式编译成指令后,uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:

  1. 为 uniqueInstance 分配内存空间
  2. 初始化 uniqueInstance
  3. 将 uniqueInstance 指向分配的内存地址

我们JVM的底层可能为了性能,这个顺序可能会被打乱, 执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。

3.静态内部类

复制代码
public class StaticInnerClassSingleton {
    private StaticInnerClassSingleton() {}

    public static StaticInnerClassSingleton getInstance() {
        return Holder.INSTANCE;
    }

    private static class Holder {
        private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
    }
}

这种实现利用了Java的类加载机制来保证实例的唯一性和线程安全性,无需额外的同步机制。

相关推荐
苏三说技术16 分钟前
DeepSeek Harness必装的10个插件
后端
程序员黑豆24 分钟前
Java中的null与NullPointerException完全指南:安全处理、实战排查与面试题
java·前端·ai编程
阿弱25 分钟前
graph-core 策略合并机制的设计与用法
后端·agent
fatcoder29 分钟前
玩转Docker 08 — 实战:容器化真实后端并编排
后端·docker·容器
神奇小汤圆32 分钟前
从原始的CRUD 到高并发架构:关于秒杀系统的问题拆解与推演
后端
魔兽大山哥39 分钟前
NL2SQL 最怕越权:我在执行层叠了好几层 SQL 闸门
后端
晴天161 小时前
Electron面试题-Day19
java·javascript·electron
foggyprojects1 小时前
当 AI 输出销售额时,如何让它解释这个数字是怎么算出来的?
后端
一只叫煤球的猫1 小时前
开个新坑,从头开始完整拆解 Spring AI 2.0 的源码
后端·面试·aigc
GeekZHR1 小时前
C语言指针进阶补充6:动态内存管理、mem系列内存函数、复杂指针声明,一次补齐指针的“三大盲区“
java·c语言·算法·指针