Java
Java 并发三件套:synchronized、volatile 与 ThreadLocal
并发概念容易混淆,是因为不同工具解决的根本不是同一个问题。我的理解框架是:共享数据需要互斥,状态变化需要可见,而请求上下文可以选择隔离。
synchronized:让临界区互斥执行
synchronized 可以保证一段代码在同一把锁下互斥执行,并建立进入、退出锁之间的内存可见性。它适合保护“读取—判断—修改”这类复合操作。
锁的对象必须说清楚:实例方法锁当前对象,静态同步方法锁 Class 对象,同步代码块则锁显式传入的对象。锁对象选错,即使写了 synchronized,也可能保护不了同一份共享数据。
volatile:让线程看见最新状态
volatile 主要保证可见性,并通过内存屏障限制特定指令重排序。它适合运行开关、配置引用等状态发布场景。
它不能让复合操作变成原子操作:
volatile int count = 0;
count++;
count++ 包含读取、加一、写回。两个线程可能都读取旧值,最终丢失一次更新。此时应使用锁或 AtomicInteger。
ThreadLocal:避免线程之间共享
ThreadLocal 为每个线程提供独立变量,常用于保存一次请求中的用户身份或追踪 ID:
请求 → 拦截器解析 JWT → ThreadLocal 保存用户 ID
→ Service 获取当前用户 → 请求结束清理
它的关键风险是线程池复用。Web 请求结束后线程不会销毁,如果没有调用 remove(),旧数据可能污染下一次请求,并让 value 长期无法释放。因此通常在 finally 中清理。
三者如何选择
- 多线程修改同一份复合状态:优先考虑锁或原子类。
- 一个线程修改状态,其他线程只需及时感知:考虑 volatile。
- 数据天然属于单个请求,不应在线程之间共享:考虑 ThreadLocal。
工具选择的本质,是先判断数据到底应该共享、同步,还是隔离。