"Java concurrent programming art" of final

final reordering rules

In the following code as an example to explain the final and the final read-write reordering rules

public FinalExample{
    int a;
    final int b;
    private static FinalExample self;
    private FinalExample(){
        this.a = 1;
        this.b = 5;
    }
    
    public static FinalExample init(){ //线程A执行
        self = new FinalExample();
    }

    public static void read(){ //线程B执行
        FinalExample temp = self; // 获取引用
        int resultA = temp.a; // 读普通域
        int resultB = temp.b; // 读final域
    }
}

final written reordering rules

  • Not the sort to be compiled discouraged constructor function outside when JMM guarantee written final variable
  • The compiler will write the final field, constructor returns a barrier before inserting StoreStore

Assuming now thread A executes init (), thread B executed read (), the order of execution may be as follows:

A thread is initialized when the constructor when there might domain common to the constructor initializes reordering outside, so it is possible to acquire a domain when the thread B reads the ordinary time to zero, but in a subsequent execution, and the value It will become a given value 1.
And when the compiler encounters final domain, it will write later joined in the final StoreStore memory barriers, to ensure the implementation of final written before the end of the function construction.

final field can guarantee any thread when reading the final variable has been initialized correctly.

Re-collation of final reading

When talking about final write reordering rules, with the focus on the writing. The final field in reading also done some special treatment. Typically, acquiring a reference to the object and the object read region ordinary reordering is likely to occur. Therefore, the order of execution such as the following may occur:

read () method seems at first glance reordering will not occur, because writing resultA seems to depend on temp variable. The resultA actually rely on references in the temp a variable indirectly dependent objects temp . Although some processors will not be reordered indirectly dependent, but in case there is no lack, such as alpha processor, JMM is to avoid this reordering will indirectly dependent on the processor, it is added to the final reading of the following reordering rule:

  • JMM ensure initial object reference final reading and the initial reading of the object domain will not be reordered
  • Compiler inserts LoadLoad final barrier in front of the read domain

final field is a reference type

Written in the constructor of the members of a final reference object, and then the constructor is referenced to the outer configuration of the object is assigned to a reference variable, these two operations can not be reordered.

final引用不能从构造函数内“溢出”

写final的重排序规则虽然保证了,final域的写会在构造函数执行之前完成,并对其他线程可见。但是如果在构造函数内,引用就发生了溢出,那么就无法保证了

public EscapeFinalExample{
    int a;
    final int b;
    private static EscapeFinalExample self;
    private EscapeFinalExample(){
        this.a = 1;
        this.b = 5;
        self = this; // this引用溢出
    }
    
    public static EscapeFinalExample init(){ //线程A执行
        new EscapeFinalExample();
    }

    public static void read(){ //线程B执行
        EscapeFinalExample temp = self; // 获取引用
        int resultA = temp.a; // 读普通域
        int resultB = temp.b; // 读final域
    }
}

假设线程A执行init(),线程B执行read(),这里的A线程还未完成完整的初始化方法,对象引用就被B可见了。即使代码上 this溢出操作放在最后,仍然有可能被重排序。它们的执行时序如下所示:

上图可以看出,构造函数还没有完成时,final域对其他线程不可见。只有在完成了构造函数后,final域才对其他线程可见。

final语义的特殊例子

在X86处理器上,由于不会发生写写、读写、读读的重排序,所以没有StoreStore内存指令,故在使用final时,编译器会忽略StoreStore内存屏障,同样LoadLoad内存屏障也会被忽略。也就是说,在x86处理器上,final是不做任何处理的。

为什么要增强final语义呢

一方面是final本身是不可修改的,其他线程不该看到final的变化。比如一开始线程读取final值为默认值0,过一段时间再读这个final变量,final值变为值1(被初始化后)。
所以新的模型就保证了,只要正确的完成构造函数(不发生this溢出),即使不用同步,也可以保证其他线程见到final初始化后的值。

Guess you like

Origin www.cnblogs.com/codeleven/p/10963065.html