Java中线程安全的单例模式必须满足延迟加载、线程安全、无性能损耗,推荐使用volatile+双重检查锁、静态内部类或枚举实现;单例对象唯一不等于其字段线程安全,可变状态需额外同步或原子类型保护。

为什么直接用静态变量存状态会出问题
因为 static 字段本身不带同步语义,多个线程同时读写一个 int 或 String 类型的静态变量,会出现值被覆盖、丢失更新、甚至读到“半初始化”状态。比如两个线程都执行 counter++,最终结果可能只加了 1 次而不是 2 次——这不是 bug,是 CPU 指令重排 + 缓存可见性缺失的必然结果。
Java 中最稳妥的懒汉式单例写法
必须同时满足:延迟加载、线程安全、无性能损耗。靠的是 volatile + 双重检查锁:
public class GlobalState {
private static volatile GlobalState instance;
private int mode;
private String lastError;
private GlobalState() {} // 私有构造,杜绝 new
public static GlobalState getInstance() {
if (instance == null) {
synchronized (GlobalState.class) {
if (instance == null) {
instance = new GlobalState();
}
}
}
return instance;
}
public void setMode(int mode) {
this.mode = mode; // 注意:这里仍需额外同步
}
}
-
volatile防止 JVM 指令重排序导致其他线程看到未完全构造的对象 - 外层
if减少锁竞争;内层if防止重复初始化 - 但
setMode这类写操作仍需自己加锁或改用AtomicInteger等原子类型
真正线程安全的状态字段怎么设
单例对象本身是唯一的,不代表它的字段自动线程安全。私有字段(如 mode)若被多线程并发修改,必须显式保护:
- 用
AtomicInteger替代int:直接替换private AtomicInteger mode = new AtomicInteger();,调用mode.set(1)或mode.incrementAndGet() - 读写频繁且逻辑复杂时,用
synchronized(this)包裹整个业务块,避免只锁 getter/setter - 避免在单例里存可变对象引用(如
List),否则要确保该对象自身也线程安全,或每次返回副本
Spring 里的单例不是万能的
Spring 默认的 @Scope("singleton") 只保证 Bean 实例唯一,不等于线程安全。如果这个 Bean 里有私有字段(比如 private User currentUser),多个请求共用它就会相互污染。
- 无状态 Bean(只做计算、不存数据)可放心用 singleton
- 有状态 Bean 必须用
@Scope("prototype")或ThreadLocal隔离字段 - 别指望 Spring 自动帮你加锁——它连你字段是不是可变都不知道
真正难的从来不是“怎么写出单例”,而是想清楚哪些字段需要跨线程共享、哪些必须隔离。漏掉一个 volatile 或一处没加锁的写操作,就可能让整个状态管理器在高并发下静默失效。

















