Java多线程单例为什么不安全 Java单例模式并发漏洞分析【面试】

风辰吖_8414

风辰吖_8414

2026-04-06

407人浏览

原创

双重检查锁定不安全因new非原子操作且jvm可能重排序,导致返回未初始化对象;加volatile可禁止重排序并保证可见性;静态内部类利用类加载机制实现线程安全懒汉式;枚举单例由jvm保障唯一性,天然防反射和反序列化攻击;synchronized(this)无效因实例尚未创建。

java多线程单例为什么不安全 java单例模式并发漏洞分析【面试】

双重检查锁定为什么 still 不安全?

因为 instance = new Singleton() 不是原子操作,JVM 可能重排序:分配内存 → 初始化对象 → 赋值引用。线程 A 执行到赋值但尚未初始化完成时,线程 B 进入 if (instance == null) 判断,发现非空就直接返回未初始化的 instance,触发 NullPointerException 或诡异状态。

修复方式只有一条硬规则:private static volatile Singleton instance; —— volatile 禁止指令重排序,并保证可见性。

  • 不加 volatile,即使有双重检查,仍可能返回半初始化对象
  • synchronized 方法块或整个 getInstance() 虽安全但性能差,失去懒汉意义
  • JDK 5+ 才真正支持 volatile 的禁止重排序语义,老 JVM 不行

静态内部类方式为什么线程安全且高效?

利用 JVM 类加载机制的天然同步性:外部类加载时,SingletonHolder 不被加载;首次调用 getInstance() 触发 SingletonHolder.class 加载,JVM 保证该过程单次、线程安全、且自动加锁。

示例关键片段:

private static class SingletonHolder {
    private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
    return SingletonHolder.INSTANCE;
}
  • 无显式同步,无 volatile,无竞态,懒加载,性能接近直接访问静态字段
  • 依赖 JVM 规范对“类初始化”阶段的严格定义,不是 Java 语言层的技巧
  • 不能用于需要传参初始化的场景(构造函数带参数)

枚举单例为何能防反射和反序列化攻击?

普通单例在反射调用 Constructor.setAccessible(true) 后可绕过私有构造器创建新实例;反序列化时若未重写 readResolve(),也会生成第二个对象。而枚举类型由 JVM 保障实例唯一性,Enum 构造器禁止反射调用,且默认实现 readResolve 返回原有实例。

  • 反射尝试 SingletonEnum.class.getDeclaredConstructor().newInstance() 会抛出 java.lang.NoSuchMethodException
  • 反序列化任意字节流得到的仍是同一个枚举常量,无需额外防御代码
  • 缺点:无法继承、不能实现延迟初始化(所有枚举实例在类加载时即创建)

为什么 synchronized(this) 在单例里完全无效?

因为 this 是实例对象,而单例还没创建出来 —— 第一次调用 getInstance() 时 this 根本不存在。常见错误写法是把 synchronized 加在非静态方法上,等价于 synchronized(this),此时锁的是将要被创建的实例,毫无意义。

  • 必须锁类对象:synchronized(Singleton.class) 或静态同步方法
  • 锁实例对象在单例场景下等于没锁,多个线程会同时进入临界区
  • IDE 通常会警告 “synchronization on local variable”,就是这个原因
真正难的不是记住哪几种写法,而是理解每种写法背后依赖的底层机制:JVM 类加载顺序、内存模型对 volatile 的支持、枚举的特殊地位。面试时如果只背代码不讲清楚这些,漏洞就藏在你张嘴的第一句话里。

Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

2023.09.22

529

3

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

2024.03.01

1658

6

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

2023.10.24

5824

49

c++中volatile关键字的作用
c++中volatile关键字的作用

本专题整合了c++中volatile关键字的相关内容,阅读专题下面的文章了解更多详细内容。

2025.10.23

694

12

class在c语言中的意思
class在c语言中的意思

在C语言中,"class" 是一个关键字,用于定义一个类。想了解更多class的相关内容,可以阅读本专题下面的文章。

2024.01.03

3316

7

python中class的含义
python中class的含义

本专题整合了python中class的相关内容,阅读专题下面的文章了解更多详细内容。

2025.12.06

286

9

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

2023.08.10

3638

6

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

2025.12.24

1029

20

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

2026.01.21

363

30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.6万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 133.3万人学习