JMM: final域的内存语义

与volatile和锁相比,对final域的读写更像是普通变量的访问。

final域的重排序规则

对于final域编译器和处理器需要遵守两个重排序规则:

  1. 构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
  2. 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

写final域的重排序规则

写final域的重排序规则禁止把final域的写重排序到构造函数之外。

  1. JMM禁止编译器把final域的写重排序到构造函数之外。
  2. 编译器会在final域的写之后,构造函数return之前,插入一个StoreStore内存屏障,禁止处理器把final域的写重排序到构造函数之外。

写final域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通变量不具有这个保障

读final域的重排序规则

读final域的重排序规则是,在一个线程中,初次读对象引用与初次读该对象的final域,编译器会在读final域之前插入一个LoadLoad内存屏障,JMM禁止处理器重排序这两个操作。

读final域的重排序规则可以确保:在读一个对象的final域之前,一定会读包含这个final域对象的引用,而普通变量不具有这个保障

final域为引用类型

前面说的final域是基本类型,对于引用类型,写final域的重排序规则对编译器和处理器增加了如下约束:

在构造函数内,对一个final引用的对象的域进行写操作,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。

final语义增强

在旧的Java内存模型中,一个最严重的缺陷就是线程可能看到final域的值会改变。例如,一个线程看到一个还未初始化之前的默认值,过一段时间再去读这个final域,就会发现被某个线程初始化之后的值。

JSR-133增强了final的语义,可以提供初始化安全保证:只要对象是正确构造的(被构造对象的引用在构造函数中没有逸出),那么不需要使用同步(如lock和volatile)就可以保证任意线程都能看到final域被初始化后的值。