J:Hi,T。
T:Hi,J。
J:今天打算介绍点什么呢?
T:今天我将介绍Java中的final关键字,这也是我们Java并发编程理论知识学习的最后一课了。
J:哦,那我们就赶快开始吧。
T:在Java中,定义为final的字段只初始化一次,在正常情况下将不能再被改变(利用反射可以改变final字段的值),编译器可以保留final字段在寄存器中,不用从主存中重载它。
使用final字段可以构建不可变的对象,不可变的对象始终都是线程安全的,可以在多线程环境中安全的使用。
J:既然使用不可变对象可以保证线程安全性,那我们就应该尽量多使用不可变对象,是吧?
T:是的,尽量使用不可变对象,隔离可变状态,可以减少多线程故障。final还可以用于发布线程安全的对象,对于正确创建的对象,无论它是如何发布的,所有线程都将看到构造函数设置的final字段的值。看下面的例子:
public class Test {
final int x;
int y;
static Test f;
public Test() {
x = 3;
y = 4;
}
static void writer() {
f = new Test();
}
static void reader() {
if (f != null) {
int i = f.x; // 保证能够看到3
int j = f.y; // 可能看到0
}
}
}
更进一步,一个正确创建的对象中,任何可以通过其final域触及到的变量(比如一个final数组中的元素,或者一个final域引用的HashMap里面的内容),也可以保证对其他线程都是可见的。看例子:
public class Test {
private final Map<String, String> capitalMap;
//不可变对象保证了初始化的安全性
public Test() {
capitalMap = new HashMap<String, String>();
capitalMap.put("SC", "CD");
capitalMap.put("YN", "KM");
// ......
capitalMap.put("XZ", "LS");
}
public String getCapital(String province) {
return capitalMap.get(province);
}
}
对于含有final字段的对象,初始化安全性可以抑制重排序,否则这些重排序会发生在对象的构造期间以及内部加载对象引用的时刻。所有构造函数要写入值的final字段,以及任何通过这些final字段可以到达的任何变量,都会在构造函数,完成后被冻结,而且可以保证任何获得该引用的线程,至少可以看到和冻结值一样新的值。用于向通过final字段可到达的初始变量写入值的操作,不会和该字段构造后的操作一起被重排序。因此上面的代码虽然没有使用同步,而且依赖于非线程安全的HashMap,但却可以被安全的发布。
J:看来final字段是非常有用的。
T:是的,今天的内容就结束了。到这里,Java并发编程的理论学习就结束了,再见。
J:再见。