线程安全的单例模式
一、饿汉式
1、在static属性中实例化(类加载的初始化阶段实例化(在准备阶段分配内存))
2、在static代码块中实例化(类加载的初始化阶段实例化)
3、枚举实现(https://www.cnblogs.com/yangyongjie/p/11056454.html)
二、懒汉式
1、同步方法或同步代码块
2、双重检查锁
在Java多线程程序中,有时候需要采用延迟初始化来降低初始化类和创建对象的开销,在使用这些对象时才进行初始化。延迟初始化需要注意线程安全
问题,否则就容易出现问题。
单例模式在获取实例的方法中,若只判断实例是否为null,是则创建对象,否则获取对象。这种方法在多线程执行的时候必然会有线程安全问题。若获取
实例方法加synchronized关键字,则能实现线程同步解决线程安全问题,但是多线程下频繁调用会造成巨大的性能开销。
1、双重检查锁及其错误根源
双重检查锁是常见的延迟初始化技术,初衷是用它来降低同步的开销,但是它是错误的用法。
双重检查锁代码:
public class DoubleCheckLock { private static Instance instance; public static Instance getInstance(){ // 第一次检查 if(instance==null){ // 第一次检查为null再进行加锁,降低同步带来的性能开销 synchronized (DoubleCheckLock.class){ // 第二次检查 if(instance==null){ // 问题出在此处 instance=new Instance(); } } } return instance; } }
错误根源:
当第一次检查时,读取instance不为null时,instance引用的对象可能还没有完成初始化!原因在于多线程下的重排序。
instance=new Instance() new创建一个对象不是原子操作,分为三步:
1)分配对象的内存空间
2)初始化对象
3)设置instance引用指向刚分配的内存地址
但是在一些编译器上(如JIT),2)和3)可能会发生重排序。
Java规范保证了重排序不会改变单线程内的程序执行结果。但是在多线程下,若线程A在执行instance=new Instance();时发生了重排序,先执行了3),
这时候线程B刚好获取到了instance不为null,接着去访问对象。但是这个时候线程A还未执行2),即还没被线程A初始化,那么这个时候线程B得到的就是
一个还没有初始化的对象。
解决方案:
(1)不允许2)和3)重排序
(2)允许2)和3)重排序,但不允许其他线程“看到”这个重排序
2、基于volatile的解决方案
通过将instance声明为volatile型来禁止2)和3)之间的重排序
public class VolatileDoubleCheckLock { // 将instance声明为volatile型 private volatile static Instance instance; public static Instance getInstance(){ // 第一次检查 if(instance==null){ // 第一次检查为null再进行加锁,降低同步带来的性能开销 synchronized (VolatileDoubleCheckLock.class){ // 第二次检查 if(instance==null){ // 多线程下将禁止2)和3)之间的重排序 instance=new Instance(); } } } return instance; } }
3、基于类初始化的解决方案
JVM在类的初始化阶段(即被Class加载后,且被线程使用之前),会执行类的初始化。在执行类的初始化期间,JVM会去获取一个锁(Class对象的初始化锁)。这个锁可以同步多个线程对一个类的初始化。
public class InstanceFactory { private static class InstanceHolder{ public static Instance instance=new Instance(); } public static Instance getInstance(){ return InstanceHolder.instance; } }
在多线程下,第一个执行getInstance方法的线程会先初始化InstanceHolder类。多个线程的情况下也只有一个线程能获取到InstanceHolder类的Class对象的初始化锁。第一个获取到nstanceHolder类的Class对象的初始化锁的线程将初始化InstanceHolder类中的静态属性instance。根据happens-before关系,其他线程将知道InstanceHolder类已被初始化,将结束初始化过程直接访问InstanceHolder。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
2019-03-22 Java高频面试题