final字段通过jmm重排序限制和可见性保证实现安全发布:其写入不被重排到构造函数外,首次读引用必见正确初始值,但仅限final字段且需对象正确构造。

Java中属性的安全发布,核心是让其他线程能**正确看到已完全初始化的对象状态**,而不是一个“半构造好”的中间态。final关键字正是实现这一目标的关键机制之一——它不是靠加锁,而是靠JVM内存模型的语义保证。
final字段如何触发安全发布
当一个对象的某个字段被声明为final,并在构造器中完成初始化(且构造器正常结束),JVM会强制保证:一旦该对象的引用对其他线程可见,那么这个final字段的值也一定对其他线程可见,且是构造器中写入的那个值。
这背后依赖的是Java内存模型(JMM)对final字段的特殊规则:
- 禁止编译器和处理器对final字段的写操作与构造器中其他操作进行重排序(尤其是不能把final字段赋值“拖后”)
- 禁止将对象引用的发布(即把this引用暴露出去)提前到final字段赋值之前
- 其他线程读取该final字段时,无需同步就能看到正确的值(即“无条件可见性”)
必须满足的前提条件
final本身不自动带来安全发布,它只在**正确使用时才生效**。以下三点缺一不可:
- 字段必须是final修饰的(包括基本类型或引用类型)
- 必须在构造器内完成初始化(不能延迟到构造器外、不能通过setter设置)
- 构造器执行完毕前,不能让this引用“逃逸”(比如在构造器里启动新线程、注册监听器、将this传给静态集合等)
例如:
错误写法(this逃逸):
public class BadExample {<br> private final int value;<br> public BadExample(int v) {<br> this.value = v;<br> registerThis(); // this被传出去,此时value可能还没写完<br> }<br> private void registerThis() { GlobalHolder.add(this); }<br>}
final引用 ≠ 对象内容不可变
final保证的是“引用不变”,不是“对象不可变”。比如:
-
final List<string> list = new ArrayList();</string>→ list变量不能再指向别的List,但list.add("x")完全合法 - 其他线程看到这个list引用后,能安全读到它指向哪个ArrayList实例,但无法保证list内部元素的一致性
- 若要整个对象安全发布,需确保final引用的对象本身也是不可变的(如String、Integer、Collections.unmodifiableList包装后的列表)
对比其他安全发布方式
final是轻量、零开销的安全发布手段,适用于构造期就确定且不再变化的属性。它和以下方式有本质区别:
- static final + 初始化块:适用于类级别常量,由类加载器保证安全发布
- volatile引用:可保证引用本身的可见性,但不保证对象内部字段的初始化完成
- synchronized / Lock:提供排他性和可见性双重保障,但有性能开销
- java.util.concurrent包工具(如AtomicReference):适合需要后续修改的场景,但构造阶段仍需final配合
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











