news 2026/4/30 12:25:03

【JAVA基础面经】线程安全的单例模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【JAVA基础面经】线程安全的单例模式

文章目录

  • 单例模式(Singleton Pattern)
    • 一、饿汉模式
    • 二、懒汉模式
    • 解决懒汉式线程安全问题
    • 双重校验锁提高并发性能
    • 静态内部类(JDK 1.2+)
  • 最佳方法:枚举方式(JDK 1.5+)
  • 方法的对比

单例模式(Singleton Pattern)

单例模式即代码中的某个类 只能有一个实例,不能有多个 。单例模式有两种主要实现方式,饿汉模式和懒汉模式

一、饿汉模式

在类加载阶段就会直接创建唯一实例,步骤如下

  1. 首先使用static创建一个实例,并立即进行实例化
  • 经过static修饰的成员,准确来说应该称为“类成员”,修饰为对应的类属性或类方法
    一个JAVA程序中,一个类对象存在一份(类名.class文件被加载到JVM内存中生成的一个对象),那么类成员(static修饰的成员)也存在一份。
  1. 为了防止再重新new一个实例,需要把构造方法设置成private

  2. 提供能拿到唯一实例的方法

classSingleton{privatestaticSingletoninstance=newSingleton();privateSingleton(){}publicstaticSingletongetInstance(){returninstance;}}publicclassDemo{publicstaticvoidmain(String[]args){//Singleton singleton = new Singleton();//errorSingletonsingleton=Singleton.getInstance();}}

线程安全看多个线程同时调用getInstance()方法时是否会出现错误,饿汉模式中的getInstance仅读取变量内容,如果多个线程同时读一个变量,此时线程是安全的

二、懒汉模式

懒汉模式不会立即初始化实例,而是等到使用的时候再创建。在饿汉模式的基础上进行了修改

classSingleton{privatestaticSingletoninstance=null;privateSingleton(){}publicstaticSingletongetInstance(){if(instance==null){instance=newSingleton2();}returninstance;}}publicclassDemo{publicstaticvoidmain(String[]args){Singleton2singleton=Singleton.getInstance();}}

懒汉模式中的getInstance()方法既包含了读操作,又包含了修改操作,是非原子性的,可能导致实例被创建出多份(两个线程同时读为null,同时创建新的实例),存在线程不安全问题

解决懒汉式线程安全问题

(1)synchronized加锁:可以通过加锁将操作打包成原子的来保障线程安全,这里的类对象作为锁对象

classSingleton{privatestaticSingletoninstance=null;privateSingleton(){}publicstaticSingletongetInstance(){synchronized(Singleton.class){if(instance==null){instance=newSingleton();}}returninstance;}}

线程不安全是发生在instance被初始化前,未初始化时多线程调用 getInstance 可能同时涉及到读和修改,一旦 instance 初始化之后,仅存在读操作,线程也就安全了。这样初始化后及时线程安全了,每次调用 getInstance 方法都需要进行加锁,从而产生锁竞争的问题。

双重校验锁提高并发性能

(2)加外层 if(双重检查):对应的改进方案,再添加一个判定条件,让instance初始化之前进行加锁,初始化后就不进行加锁了。里层的 if 条件不能进行省略,在两个 if 条件判断的时间差内,可能存在instance的修改操作,若去掉里层的 if 那么就没有将读写操作进行原子性打包。

此时对象一旦初始化完成,后续所有线程请求都只走最外层那个 if (instance == null),根本不进入 synchronized 块,提高了并发性能。

classSingleton{publicstaticSingletongetInstance(){if(instance==null){// 1. 先看一眼(无锁)synchronized(Singleton.class){// 2. 发现是空的?才去排队if(instance==null){// 3. 排到了再确认一下instance=newSingleton();}}}returninstance;// 4. 大多数情况直接返回,不碰锁}}

(3)volatile 修饰防止指令重排序:如果多个线程块都去调用 getInstance 方法,大量的读操作会产生编译器优化,编译器和处理器为了优化性能,可能会改变指令执行顺序

对于下面的代码,new Singleton() 可以分为三个步骤:

  1. 分配内存空间
  2. 初始化对象
  3. 将引用指向内存地址

编译器和 CPU 可能为了优化,将 步骤 2 和步骤 3 交换(单线程下无影响),此时存在线程A、B均调用 getInstance() 方法

  • A线程:刚执行完步骤 3(还没初始化)
  • B线程: 过来看到 instance != null 直接返回了
classSingleton{privatestaticvolatileSingletoninstance=null;//加上 volatile 禁止重排序privateSingleton2(){}publicstaticSingletongetInstance(){if(instance==null){//B线程发现instance != null,但是此时的instance还未初始化synchronized(Singleton.class){if(instance==null){instance=newSingleton();//A线程将引用指向内存地址,但还未初始化对象}}}returninstance;}}

静态内部类(JDK 1.2+)

加 volatile 的双重检查锁已经足够完美,但代码依然略显繁琐且容易写错。静态内部类单例模式利用 JVM 的类加载机制天然保证了线程安全与懒加载,是实际开发中最推荐的写法。

外部类 Singleton 被加载时,其内部的静态内部类 Holder 不会被立即加载。只有当调用 getInstance() 方法,首次访问 Holder.INSTANCE 时,才会触发 Holder 类的加载与初始化

// 完美版:不用锁,不用 volatile,JVM 保证线程安全且懒加载publicclassSingleton{privateSingleton(){}privatestaticclassHolder{staticfinalSingletonINSTANCE=newSingleton();}publicstaticSingletongetInstance(){returnHolder.INSTANCE;}}

最佳方法:枚举方式(JDK 1.5+)

此时的INSTANCE; 是这个枚举类的唯一一个实例,经过编译器处理后生成了一个 public static final 的常量

publicfinalclassSingletonextendsjava.lang.Enum<Singleton>{publicstaticfinalSingletonINSTANCE=newSingleton();......}

枚举实例是在枚举类被加载时初始化的,相当于饿汉模式

对于枚举,Java 在反射 API 底层做了硬性拦截。同时,枚举的序列化机制是 JVM 特殊处理的,不遵循普通类的反序列化逻辑,当反序列化一个枚举对象时,JVM 会根据流中的枚举常量名称(比如 “INSTANCE”),直接返回该枚举类中同名的那个 static final 常量,而不是重新构造一个对象。因此该方法绝对防止多次实例化,天然抵御反射攻击和序列化破坏

publicenumSingleton{INSTANCE;// 定义一个枚举元素,它就代表了 Singleton 的一个实例// 可以像普通类一样添加成员变量和成员方法privateStringconfigInfo="default";publicvoiddoSomething(){System.out.println("通过枚举实现单例模式");}publicStringgetConfigInfo(){returnconfigInfo;}}// 使用方式publicclassDemo{publicstaticvoidmain(String[]args){Singletonsingleton=Singleton.INSTANCE;singleton.doSomething();}}

方法的对比

实现方式是否线程安全是否懒加载并发性能反射攻击和序列化破坏
饿汉模式安全非懒加载无法防御
懒汉模式(无锁)不安全懒加载无法防御
懒汉模式(方法加锁)安全懒加载无法防御
双重检查锁 + volatile安全懒加载较好无法防御
静态内部类安全懒加载优秀无法防御
枚举(Enum)安全非懒加载绝对防御
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/30 12:23:59

《小坦克大战小怪兽》小游戏实战一:关于自己小游戏再次暴死的反思

《小坦克大战小怪兽》&#xff0c;这算是自己继上一个独立小游戏《超市出货王》后&#xff0c;又耗费了半年左右的空余时间的心血垃圾之作了。吸取了上一次白嫖UI资源所带来的一无所获苦痛后&#xff0c;这一次我算是咬牙花了重金购入了一套相对完整的小游戏美术资源了。而就在…

作者头像 李华
网站建设 2026/4/30 12:23:39

Hermes Agent 完整使用教程

Hermes Agent 完整使用教程 目录 Hermes 是什么安装与初始化第一次进入 CLI配置、模型与 profile工具、toolset 与执行边界skill、memory 与上下文文件MCP 接入外部系统Gateway 与多平台使用cron、后台任务与自动化开发扩展调试、测试与排障最佳实践 1. Hermes 是什么 Hermes A…

作者头像 李华
网站建设 2026/4/30 12:24:28

查重踩坑退退退!PaperXie 四大查重功能,本科生闭眼过查重关

paperxie-免费查重复率aigc检测/开题报告/毕业论文/智能排版/文献综述/AIPPThttps://www.paperxie.cn/checkhttps://www.paperxie.cn/check 一、 毕业论文查重&#xff1a;那些年我们踩过的坑 提到毕业论文查重&#xff0c;每个本科生都能说出一肚子苦水。 有人为了省钱用不知…

作者头像 李华
网站建设 2026/4/11 3:12:42

科研告急?无人机小麦倒伏实验,一键搞定![特殊字符]

各位科研er&#xff0c;是不是还在为小麦倒伏实验发愁&#xff1f;&#x1f914; 传统人工调查费时费力&#xff0c;数据精度还不够&#xff1f;论文开题、中期考核、毕业在即&#xff0c;实验进度却卡壳了&#xff1f;别慌&#xff0c;救星来了&#xff01;&#x1f31f;我们提…

作者头像 李华
网站建设 2026/4/12 11:26:03

使用ffmpeg本地发布rtmp/rtsp直播流

1 FFmpeg的选择与下载 选择合适的版本 首先&#xff0c;访问FFmpeg官方下载页面&#xff08;https://ffmpeg.org/download.html&#xff09;&#xff0c;在这里可以找到最新的稳定版或 nightly 构建版本。对于大多数常规用途&#xff0c;推荐下载预编译的Windows静态版本&a…

作者头像 李华
网站建设 2026/4/11 3:08:35

表面处理---阳极氧化和电泳喷涂

表面处理---阳极氧化和电泳喷涂 铝型材表面处理包括阳极氧化、电泳涂漆和静电喷涂。 阳极氧化是一种金属或合金的电化学氧化。铝及其合金在相应的电解液和特定的工艺条件下,外加电流的作用,可在铝制品(阳极)上形成一层氧化膜。阳极氧化可提高铝合金表面硬度、耐磨性和耐腐…

作者头像 李华