返回首页
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。

工具选择的本质,是先判断数据到底应该共享、同步,还是隔离。