11.1.4 final关键词

在Java语言中,final关键词有很多语义,比如声明为final的类无法被继承,声明为final的方法无法在子类中被覆写。从内存模型的角度来说,final关键词最重要的语义是声明一个域的值只能被初始化一次。在初始化之后,该域的值无法被修改。在多线程程序开发中,final域通常用来实现不可变对象(immutable object)。当对象中的共享变量的值不可能发生变化时,在多线程访问时不会出现问题,也就不需要使用线程之间的同步机制来进行处理。在Java标准库中最常用的不可变对象是String类的对象。在多线程程序中,应该尽可能地使用不可变对象,以避免使用同步机制。代码清单11-4给出了不可变对象的类的声明方式。把类中所有域声明为final,并在构造方法中进行初始化。

代码清单11-4 不可变对象的类的声明方式


public class User{

private final String name;

private final String email;

public User(String name, String email){

this.name=name;

this.email=email;

}

}


在构造方法成功完成之前,要确保正在创建的对象的引用不会被其他线程访问到,否则,其他线程可能看到部分创建完成的对象。代码清单11-5给出了一个错误的示例。在WrongUser类的构造方法中,把当前对象的引用赋值给UserHolder类的静态变量user,会导致使用UserHolder类的线程看到尚未创建完成的WrongUser类的对象。该对象中包含的变量可能没有被初始化成正确的值。

代码清单11-5 在构造方法中添加当前对象的引用的错误示例


public class WrongUser{

private final String name;

public WrongUser(String name){

UserHolder.user=this;

this.name=name;

}

}


如果一个线程是在对象的构造方法成功完成之后才通过该对象的引用来进行访问的,那么该线程肯定可以看到对象中的final域被初始化之后的值。如果域没有被声明为final,则构造方法完成之后,其他线程不一定可以看到这个域被初始化之后的值,而有可能看到域的默认值。由于final域具有这些特征,编译器对final域的处理是很灵活的。这些域可能被随意地与其他代码进行重新排列。在代码执行时,final域的值可以被保存在寄存器中,而不用从主存中频繁重新读取。