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

单例模式(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的类加载机制来保证实例的唯一性和线程安全性,无需额外的同步机制。

相关推荐
bobz965几秒前
tls ingress 简单记录
后端
皮皮林5511 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友1 小时前
什么是OpenSSL
后端·安全·程序员
bobz9651 小时前
mcp 直接操作浏览器
后端
前端小张同学4 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook4 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康5 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在5 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net
文心快码BaiduComate5 小时前
文心快码入选2025服贸会“数智影响力”先锋案例
前端·后端·程序员
neoooo5 小时前
🌐 Cloudflare Tunnel vs ZeroTier:两个世界的内网穿透哲学
后端