Java线程安全核心是优先消除共享:用ThreadLocal、实例变量或不可变对象;必须共享时,按场景选Atomic、synchronized、ReentrantLock或volatile(仅限状态标志)。

Java 中防止多线程并发修改共享变量导致线程安全问题,核心是打破“多个线程同时读-改-写同一变量”这个危险链条。关键不在于加什么锁,而在于先判断:这个变量真有必要被所有线程共享吗?
优先消除共享——从根源上避开问题
很多线程安全问题其实源于设计阶段的过度共享。static 变量不是必须的,它只是方便,但代价很高。
- 如果变量只用于单次请求或单个任务(比如用户 ID、事务上下文、日期格式器),直接改用 ThreadLocal:每个线程独享一份副本,天然隔离,零同步开销
- 如果变量属于业务对象状态(比如订单计数器、购物车项),把它从 static 改成普通实例变量;配合 Spring 等容器管理 Bean 作用域(如 prototype 或 request-scoped),让每次调用都拿到新实例
- 对不可变对象(如 String、LocalDateTime、BigDecimal)放心使用——它们一旦创建就不能改,多线程读取完全安全
需要共享时,选对同步工具
只有当变量确实需跨线程协同更新(例如全局请求计数、配置热刷新开关),才考虑同步机制。不同场景对应不同方案:
-
单变量原子操作(如计数、开关):用
AtomicInteger、AtomicBoolean等。底层靠 CAS,比 synchronized 更轻量,且自带可见性保证。例如:counter.incrementAndGet() -
简单临界区保护(如读写一个 int、更新一个 List):用
synchronized代码块,锁对象必须是类级别(synchronized (MyClass.class)),不能用 this 或 new Object(),否则锁失效 -
复杂逻辑或需超时/中断:用
ReentrantLock。记得 lock() 后必须在 finally 块中 unlock(),避免死锁
volatile 不是万能解药,用错反而误导
volatile 只解决可见性和重排序,不解决原子性。它适合非常有限的场景:
- 纯状态标志:如
private static volatile boolean running = true;,只做赋值(running = false),不参与计算 - 初始化完成标记(double-checked locking 中的 instance 字段)
- 绝对不要用于
count++、list.add()这类“读-改-写”操作——它不会阻止两个线程同时读到旧值再各自加 1
别忽略隐式共享和容器陷阱
线程安全问题常藏在你以为“没问题”的地方:
- Spring 默认单例 Bean 里如果持有
SimpleDateFormat、ArrayList、HashMap等非线程安全对象,被线程池反复调用就会出问题。应换用DateTimeFormatter(不可变)、ConcurrentHashMap或每个线程新建实例 - 日志、缓存、数据库连接池等外部组件本身有并发控制,但你的代码若在回调或监听器里修改了共享变量,仍需自行同步
- 局部变量、方法参数、new 出的对象默认线程封闭,只要不逃逸到其他线程,就不用管
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











