重排序是编译器和处理器的默认行为,旨在提高性能,但在多线程环境中可能引发问题,因此需要通过同步机制进行控制。
final关键字详解
final
,用于修饰类、方法和变量。它的主要作用是限制继承、重写和修改,从而增强代码的安全性、可读性和性能优化。
- final 修饰变量
- 作用:
final
修饰的变量一旦被赋值,其值不可更改。 - 分类:
- 局部变量:必须在声明时或使用前赋值,且赋值后不可修改。
- 成员变量:必须在声明时、构造方法中或初始化块中赋值,且赋值后不可修改。
- 静态变量(类变量):必须在声明时或静态初始化块中赋值,且赋值后不可修改。
- 示例:
final int x = 10; // x = 20; // 编译错误,x 不可修改
- 注意:
- 对于引用类型变量,
final
限制的是引用不可变,但对象内部的状态可以修改。 - 例如:
final List<String> list = new ArrayList<>(); list.add("Hello"); // 合法 // list = new ArrayList<>(); // 编译错误
- 对于引用类型变量,
- 作用:
- final 修饰方法
- 作用:
final
修饰的方法不可被子类重写。 - 适用场景:
- 当方法的行为已经固定,不希望子类改变其实现时使用。
- 例如,
Object
类中的getClass()
方法就是final
方法。
- 示例:
class Parent { final void show() { System.out.println("Parent"); } } class Child extends Parent { // void show() { // 编译错误,无法重写 final 方法 // System.out.println("Child"); // } }
- 作用:
- final 修饰类
- 作用:
final
修饰的类不可被继承。 - 适用场景:
- 当类的设计已经完善,不希望被扩展时使用。
- 例如,Java 中的
String
、Integer
等类都是final
类。
- 示例:
final class MyClass { // 类内容 } // class SubClass extends MyClass { // 编译错误,无法继承 final 类 // }
- 作用:
- final 关键字的优势
- 安全性:防止变量被意外修改,或类和方法被不恰当地扩展或重写。
- 可读性:明确表明变量、方法或类的不可变性,便于理解和维护。
- 性能优化:
final
变量和方法可以被 JVM 优化,例如内联优化(inline optimization)。
- final 与不可变对象
- 不可变对象:对象的状态在创建后不可修改。
- 关系:
final
关键字是实现不可变对象的重要手段。 - 示例:
public final class ImmutableClass { private final int value; public ImmutableClass(int value) { this.value = value; } public int getValue() { return value; } }
- final 的使用注意事项
- 过度使用:滥用
final
可能导致代码灵活性降低,应适度使用。 - 与并发编程的关系:
final
变量在多线程环境下是线程安全的,因为其值不可变。
- 过度使用:滥用
final关键字重排序规则
final
修饰的数据类型分类
-
基本数据类型
- final域写:
- 规则:禁止将
final
域的赋值操作重排序到构造方法之外。 - 目的:确保当一个对象对所有线程可见时,其所有
final
域都已经正确初始化。 - 关键点:
final
域的赋值必须在构造方法内完成,不能延迟到构造方法之外。
- 规则:禁止将
- final域读:
- 规则:禁止初次读取对象的引用与读取该对象包含的
final
域的重排序。 - 目的:保证在读取对象引用时,其
final
域的值已经正确初始化。 - 关键点:初次读取对象引用和读取
final
域的操作必须按顺序执行,不能重排序。
- 规则:禁止初次读取对象的引用与读取该对象包含的
- final域写:
-
引用数据类型
- 扩展规则:
- 在基本数据类型的规则基础上,增加一条约束:禁止在构造函数中对
final
修饰的对象的成员域的写入与随后将该对象的引用赋值给引用变量重排序。
- 在基本数据类型的规则基础上,增加一条约束:禁止在构造函数中对
- 目的:确保在将对象的引用赋值给引用变量时,其
final
域的成员已经正确初始化。 - 关键点:如果
final
域是一个引用类型,那么对该引用类型成员的赋值操作不能与将对象引用赋值给变量的操作重排序。
- 扩展规则:
总结
- 基本数据类型:
final
域的赋值必须在构造方法内完成,且读取对象引用和读取final
域的操作不能重排序。 - 引用数据类型:在基本数据类型规则的基础上,增加对
final
域成员赋值的顺序约束,确保在对象引用赋值时,其final
域成员已经初始化。
这些规则共同保证了final
域在多线程环境下的可见性和正确性,避免了由于重排序导致的线程安全问题。
对象引用什么时候被其他线程可见
对象引用对其他线程的可见性是一个与 Java 内存模型(JMM) 相关的复杂问题。对象引用的可见性取决于多个因素,包括 变量的修饰符、线程同步机制 以及 对象的初始化过程。
- 普通变量(非 final 和非 volatile)
- 默认情况:普通变量的写操作对其他线程不可见,因为 JMM 允许线程将变量值缓存在本地内存中。
- 问题:一个线程修改了对象引用,其他线程可能仍然看到旧值。
- 示例:
在多线程环境中,class MyClass { Object obj; void setObj(Object obj) { this.obj = obj; } }
obj
的修改对其他线程可能不可见。
- volatile 修饰的变量
- 作用:
volatile
修饰的变量对所有线程可见,写操作会立即刷新到主内存,读操作会从主内存读取最新值。 - 适用场景:适合用于标记状态或简单的共享变量。
- 示例:
class MyClass { volatile Object obj; void setObj(Object obj) { this.obj = obj; // 写操作对其他线程可见 } }
- 作用:
- final 修饰的变量
- 作用:
final
修饰的变量在构造方法完成后对其他线程可见,且不会被重排序到构造方法之外。 - 适用场景:适合用于不可变对象或线程安全的初始化。
- 示例:
class MyClass { final Object obj; MyClass(Object obj) { this.obj = obj; // 构造方法完成后,obj 对其他线程可见 } }
- 作用:
- 同步机制(synchronized、Lock)
- 作用:通过同步机制(如
synchronized
或Lock
)可以确保变量修改对其他线程可见。 - 适用场景:适合用于复杂的线程同步场景。
- 示例:
class MyClass { Object obj; synchronized void setObj(Object obj) { this.obj = obj; // 写操作对其他线程可见 } }
- 作用:通过同步机制(如
- 对象的初始化与可见性
- 问题:如果对象未正确初始化,其他线程可能看到部分初始化的对象。
- 解决方案:使用
final
或同步机制确保对象初始化完成后对其他线程可见。 - 示例:
class MyClass { private final Object obj; MyClass(Object obj) { this.obj = obj; // 构造方法完成后,obj 对其他线程可见 } }