final不能自动防止构造函数内this引用逸出;它仅在对象正确构造且未逸出的前提下,保证其他线程可见final字段的初始化值。一旦发生逸出(如this赋给静态变量、注册监听器或提交线程池),JMM对final的可见性保障即失效,可能导致读取到默认值。

final 不能自动兜底构造逸出
final 关键字本身不提供“防逸出”能力,它只在对象被**正确构造**的前提下,才保证其他线程能看到 final 字段的初始化值。一旦构造函数中发生 this 引用逸出——比如把 this 赋给静态变量、注册到监听器、传入线程池任务或发布到共享队列——那么其他线程就可能拿到一个尚未初始化完成的对象引用。
此时,即使字段声明为 final,JMM 的初始化安全性也不再生效。线程读取该 final 字段时,仍可能看到默认值(如 0、null、false),而非构造函数中写入的值。
逸出破坏的是“正确发布”的前提
Java 内存模型对 final 的可见性保障,依赖两个关键条件:
- final 字段必须在构造函数中完成赋值
- 对象引用不能在构造函数执行完毕前被发布(即不能逸出)
二者缺一不可。逸出相当于提前交出“半成品”,而 final 的语义不是“防半成品”,而是“保完工后的一致性”。就像锁门不等于检查门是否造好——门没装完就挂锁,别人照样能推开。
典型逸出场景与后果
以下代码演示高风险操作:
-
静态引用泄露:
public static Instance instance; public Instance() { instance = this; }—— 其他线程可能通过instance访问未初始化的 final 字段 -
监听器注册:
EventBus.register(this);—— 回调可能在构造中触发,访问 final 域时得到 null -
线程池提交:
executor.submit(() -> doSomething());(lambda 捕获 this)—— 构造未完,任务已排队执行
这些情况下,编译器和 JVM 不会报错,但运行时行为不可预测,且难以复现和调试。
如何真正保障安全
避免逸出是开发者责任,final 只是配合机制。可靠做法包括:
- 构造函数内禁止暴露 this 引用(不赋值给任何外部可访问变量或对象)
- 使用静态工厂方法替代公有构造器,将对象完全构造完毕后再返回引用
- 若必须发布,确保发布动作发生在构造函数 return 之后(例如:先 new,再 set,再 publish)
- 对含可变内部状态的 final 引用(如
final List<string></string>),还需额外防御性拷贝或封装

















