final 关键字在Java中具有多重作用,其中之一是在多线程环境下提供了可见性保证。虽然 final 本身并不是专门设计用来解决多线程可见性问题的,但是在一些特定的场景中,使用 final 可以达到保证可见性的效果。
final 与可见性的关系:
- 不可变对象:
- 当一个对象被声明为
final类型时,意味着该对象的引用和状态都是不可变的。由于不可变性,当一个线程对这个对象进行初始化后,其他线程获取到这个对象的引用时,可以确保看到的是初始化完成后的状态,因为不会发生对象状态的改变。
publicclassImmutableObject { privatefinalintvalue; publicImmutableObject(intvalue) { this.value=value; } publicintgetValue() { returnvalue; } }
在上述例子中,ImmutableObject 类的实例是不可变的,即一旦初始化后,其状态不会发生改变。如果一个线程在构造函数中初始化了该对象,其他线程获取到这个对象的引用后,可以确保看到的是初始化完成后的状态。
final字段:
- 当一个字段被声明为
final时,对这个字段的写操作将在构造函数执行完成之前完成,并且对其他线程来说,这个final字段的值是可见的。
publicclassFinalFieldExample { privatefinalintfinalField; publicFinalFieldExample() { // 对 finalField 的写操作finalField=42; } publicintgetFinalField() { // 对 finalField 的读操作returnfinalField; } }
在上述例子中,finalField 被声明为 final,并在构造函数中进行了初始化。在构造函数执行完成后,其他线程获取到 FinalFieldExample 实例的引用后,可以确保看到的是 finalField 的最终值。
final 与内存屏障:
在Java内存模型中,final 修饰的字段具有一些特殊的语义,可以被看作是一种轻量级的内存屏障。在Java 5及之后的版本中,对 final 字段的写操作会在构造函数结束前插入一个 StoreStore 屏障,保证了 final 字段的写入操作对于后续读操作的可见性。
这个特性意味着在构造函数中对 final 字段的写入操作对于其他线程来说是可见的,避免了对象的未完全构造状态被其他线程访问的情况。
使用 final 的注意事项:
- 对象引用的可见性:
- 对象引用的
final修饰仅保证引用本身的不可变性,而不保证引用对象的状态不可变。如果引用对象是可变的,其他线程仍然可以修改其状态。在使用final时,确保对象的状态也是不可变的才能更好地保证可见性。
publicclassMutableObject { privateintvalue; publicMutableObject(intvalue) { this.value=value; } publicintgetValue() { returnvalue; } publicvoidsetValue(intvalue) { this.value=value; } } publicclassFinalReferenceExample { privatefinalMutableObjectmutableObject; publicFinalReferenceExample(MutableObjectmutableObject) { this.mutableObject=mutableObject; } publicMutableObjectgetMutableObject() { returnmutableObject; } }
在上述例子中,FinalReferenceExample 类包含一个 final 引用 mutableObject,但 mutableObject 对象本身是可变的,其他线程仍然可以修改其状态。
- 构造函数中的
final字段:
- 如果在构造函数中对
final字段进行了写操作,并且其他线程在构造函数执行期间就能获取到该对象的引用,那么其他线程可能看到的是final字段的初始值而非最终值。
publicclassIncorrectFinalExample { privatefinalintfinalField; publicIncorrectFinalExample() { // 对 finalField 的写操作finalField=42; // 其他线程可能在这里获取到对象引用 } publicintgetFinalField() { // 其他线程可能看到 finalField 的初始值而非最终值returnfinalField; } }
在上述例子中,由于其他线程可能在构造函数执行期间获取到对象引用,因此它们可能看到的是 finalField 的初始值而非最终值。在使用 final 时,确保在构造函数中对 final 字段的写操作发生在其他线程获取到对象引用之后。