JMM 规定的final属性的重排序规则
1)在构造函数内对一个final属性的写入,与随后把这个执行构造方法构造的对象的引用赋值给一个变量 这两个操作不能重排序。就是说一定是先执行 final属性的写入,然后才能通过引用访问构造(new)的对象。
注意这里只保证了final属性的写入顺序,没有保证普通属性的顺序(就是说普通属性的写可能会被重排序到构造方法外执行)。
2)第一次读去一个包含final属性的实例的引用,与随后读取final属性这两个操作不能重排序。就是说肯定是先读到实例的引用,然后才会去读final属性值
final语义的实现方式
很显然 先拿到一个对象的引用 再去访问对象中的属性 ,这两个操作是有关联的。大部分处理器不会对有关联关系的两条指令进行重排序,所以在大部分的处理器中JMM其实不用做任何操作。就能保证这一点。而且一旦这两个指令重排序了,那访问的结果肯定是错误的。 对于不能自动禁止这种重排序的,JMM必须在final读取对象引用 操作和 读取final属性操作之间加 load load 屏障。同理对于不能自动禁止final写重排序的处理器,需要在构造方法中的final写入操作之后 构造方法返回前,插入 store store 屏障。
疑问 一旦加了storestore 屏障那么 构造方法中所有的写都不会重排序到构造方法外,但是规则描述中却说普通属性的写入可能被重排序到构造方法外?为啥?
包含final属性的对象引用不能从构造函数中“溢出”
简单来说就是 如果要保证上述语义的正确,就必须遵守不要在构造方法中将正在被创建的对象的引用,传递出去。示例如下图。原因也简单,一旦在构造方法中做了这个操作那么,其他线程就可以在构造方法没有执行完的情况下对对象及其属性进行访问,可能访问的时候属性还未正确赋值。这样就肯定会出问题了。
JSR-133为什么要增强final的语义
在旧的java内存模型中一个问题就是,线程可以看到final的值会改变。比如 final属性被写入前实际保存的值时默认值 比如0。写入后的值为A 。那么由于CPU可能将final写入重排序到构造方法外,线程可能会读到默认值0 ,然后再次读取又读取到修改后的值A .
为了修补这个漏洞,JSR-133 专家组增强了final的语义。只要对象是正确构造的(对象引用没有在构造方法中外溢)那么不需要线程同步,就可以看到final被初始化后的值。