814 字
4 分钟
Java线程安全与匿名内部类

✅ 一、局部变量 vs 成员变量#

特性局部变量(方法内)成员变量(类字段)
存储位置栈(Stack)堆(Heap,随对象存在)
默认值❌ 没有,默认必须显式初始化✅ 有(0, false, null 等)
生命周期方法调用期间对象存在期间
线程安全性若不逃逸 → ✅ 安全
若引用共享对象 → ❌ 可能不安全
默认 ❌ 不安全(单例 Bean 中多线程共享)

📌 关键原则:局部变量本身是线程私有的,但它引用的对象可能被共享

✅ 二、线程安全判断核心准则#

🔐 一个方法是否线程安全,取决于是否存在「多个线程共享可变状态」

  • 安全:只操作局部创建 的对象,且对象不逃逸到方法外(如不赋给成员变量、不返回、不传给共享组件)。
  • 不安全:操作了共享的可变状态(如实例变量、静态变量),且该状态非线程安全(如 ArrayList, HashMap)。

💡 “对象逃逸”是关键:即使变量是局部的,只要它指向的对象被多个线程访问,就可能不安全。

✅ 三、Lambda / 匿名内部类对局部变量的限制#

规则:#

被 lambda 或匿名内部类捕获的局部变量必须是 effectively final(实质上不可变)。

  • ✅ 允许:
    • 读取基本类型变量(如 int x = 5; System.out.println(x);
    • 调用引用类型对象的方法(如 log.append(...)
  • ❌ 禁止:
    • 修改基本类型变量(x++
    • 重新赋值引用变量(log = new StringBuilder();

为什么?#

  • 编译器会将局部变量的值(基本类型)或引用(对象地址)拷贝到内部类中;
  • 如果原变量后续被修改,会导致拷贝值与原始值不一致,引发逻辑错误;
  • 所以 Java 强制要求:被捕获的局部变量不能变

⚠️ 注意:这个限制与是否用 lambda 无关,匿名内部类同样受此约束。

✅ 四、对象 vs 变量:关键区分#

操作修改的是?是否违反 effectively final?
count++局部变量本身的值(栈上)✅ 违反
log.append("...")堆上对象的内容❌ 不违反(log 引用未变)
log = new StringBuilder()局部引用变量的值(栈上地址)✅ 违反

🧠 记住

  • 变量 ≠ 对象
  • 引用变量的值 = 地址
  • 对象的内容 = 堆上的状态

✅ 五、如何实现“可变但安全”的异步操作?#

当需要在 lambda/异步任务中修改状态时:

推荐方案:#

  1. 使用线程安全的可变容器

    AtomicInteger count = new AtomicInteger(0);
    AtomicReference<StringBuilder> logRef = new AtomicReference<>(new StringBuilder());
  2. 封装到自定义堆对象中(确保不被多线程共享):

    class Context { int count; StringBuilder log; }
    Context ctx = new Context();
    CompletableFuture.runAsync(() -> ctx.count++);

✅ 只要每个调用创建独立对象,且仅被一个异步任务使用 → 线程安全

✅ 六、Spring Bean 与线程安全#

  • Spring 默认 Bean 是 单例(Singleton)
  • 所有请求共享同一个实例;
  • 因此:
    • ❌ 避免在 Bean 中使用可变的实例变量存储请求级数据;
    • ✅ 尽量让方法无状态(stateless),只用局部变量;
    • ✅ 如需状态,使用 ThreadLocal、方法参数传递、或每次新建对象。
Java线程安全与匿名内部类
https://yanghc.dev/posts/b0518014/
作者
竹林听雨
发布于
2025-11-13
许可协议
CC BY-NC-SA 4.0