推荐用双重检查锁(DCL)+ volatile 实现线程安全单例:外层判空减少锁竞争,内层加锁保证唯一性,volatile 防止指令重排序;枚举单例可防反射和序列化攻击但不支持延迟加载与继承。

单例模式怎么写才真正线程安全
Java里最常写的 Singleton,往往在高并发下出问题。不是加了 synchronized 就万事大吉,也不是靠 static 字段就能一劳永逸。
推荐用双重检查锁(DCL)+ volatile,这是 JDK 6+ 下兼顾性能与安全的写法:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}}
-
volatile防止指令重排序,避免其他线程拿到未初始化完成的对象 - 两次
if (instance == null)判断:外层减少锁竞争,内层保证唯一性 - 禁止反射攻击?可在构造函数里加
if (instance != null) throw new RuntimeException("Singleton already initialized") - 序列化破坏单例?需实现
readResolve()方法返回instance
Spring 默认是单例,但为什么有时候像“多例”
Spring 的 @Scope("singleton") 是容器级单例——整个 ApplicationContext 中只存在一个 bean 实例,但这个“单例”不等于线程安全,也不等于全局唯一对象引用。
立即学习“Java免费学习笔记(深入)”;
- 如果 bean 有可变状态(比如含
private List),多个线程同时操作它,照样出错cache - Web 环境中,
request或session作用域的 bean 看起来“每次请求都新建”,其实是 Spring 在每次请求开始时从代理获取新实例,底层仍是工厂管理 - 想让某个 bean 每次
getBean()都返回新实例?显式声明@Scope("prototype"),但注意:prototype bean 的依赖注入仍由容器完成,只是它本身不被缓存
什么时候必须用多例(prototype),而不是硬写 new
手动 new XxxService() 看似简单,但会绕过 Spring 容器的生命周期管理、AOP 代理、依赖注入和事务控制——这在真实项目里几乎不可接受。
css3实现多款创意按钮,按钮需要用的地方太多了,例如商城网站,前台的会员登录与注册需要用到按钮,后台增删改查我们有时候也会需要用到按钮,多款创意按钮,不同样式。php中文网推荐下载!
- 需要带不同初始化参数的多个实例?用
ObjectFactory或Provider延迟获取,Spring 4.3+ 支持自动注入 - 高频创建/销毁的轻量对象(如 DTO、Builder)?直接 new 更合理,不属于 Spring 管理范畴
- 测试中模拟不同行为?用
@MockBean或@SpyBean替换 prototype bean,比改代码更可控 - 注意:
prototypebean 内部若持有 singleton bean 引用,那个 singleton 仍是共享的——多例 ≠ 全隔离
枚举实现单例真的万无一失?
Joshua Bloch 在《Effective Java》里力推枚举单例,确实能天然防止反射和序列化攻击,写法极简:
public enum Singleton {
INSTANCE;
public void doSomething() { /* ... */ }}
但它也有现实约束:
- 无法继承(枚举类隐式 final),不能实现需要扩展的抽象基类
- 不能延迟加载:JVM 加载类时就初始化所有枚举实例,哪怕你永远没调用
INSTANCE - 某些框架(如部分 RPC 序列化工具)对枚举支持不一致,可能报
IllegalArgumentException: No enum constant - 单元测试中难以 mock 枚举方法,得靠 PowerMock 或改用接口+委托
真要选枚举单例,确保它只做纯粹的状态无关工具类,不涉及外部资源或复杂初始化逻辑。









