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

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

相关推荐
微信-since811922 分钟前
[ruby on rails] 安装docker
后端·docker·ruby on rails
色空大师7 分钟前
23种设计模式
java·开发语言·设计模式
闲人一枚(学习中)7 分钟前
设计模式-创建型-建造者模式
java·设计模式·建造者模式
2202_7544215425 分钟前
生成MPSOC以及ZYNQ的启动文件BOOT.BIN的小软件
java·linux·开发语言
蓝染-惣右介28 分钟前
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
java·数据库·tomcat·mybatis
小林想被监督学习28 分钟前
idea怎么打开两个窗口,运行两个项目
java·ide·intellij-idea
HoneyMoose30 分钟前
IDEA 2024.3 版本更新主要功能介绍
java·ide·intellij-idea
我只会发热32 分钟前
Java SE 与 Java EE:基础与进阶的探索之旅
java·开发语言·java-ee
是老余33 分钟前
本地可运行,jar包运行错误【解决实例】:通过IDEA的maven package打包多模块项目
java·maven·intellij-idea·jar
crazy_wsp34 分钟前
IDEA怎么定位java类所用maven依赖版本及引用位置
java·maven·intellij-idea