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

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

相关推荐
Miqiuha5 分钟前
lock_guard和unique_lock学习总结
java·数据库·学习
一 乐1 小时前
学籍管理平台|在线学籍管理平台系统|基于Springboot+VUE的在线学籍管理平台系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
数云界1 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
阑梦清川2 小时前
Java继承、final/protected说明、super/this辨析
java·开发语言
快乐就好ya3 小时前
Java多线程
java·开发语言
IT学长编程3 小时前
计算机毕业设计 二手图书交易系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·二手图书交易系统
CS_GaoMing3 小时前
Centos7 JDK 多版本管理与 Maven 构建问题和注意!
java·开发语言·maven·centos7·java多版本
艾伦~耶格尔4 小时前
Spring Boot 三层架构开发模式入门
java·spring boot·后端·架构·三层架构
man20174 小时前
基于spring boot的篮球论坛系统
java·spring boot·后端
2401_858120534 小时前
Spring Boot框架下的大学生就业招聘平台
java·开发语言