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
字段的写操作发生在其他线程获取到对象引用之后。