Java并发拾遗(五)——final

final也是并发中一个常用的tips。JMM中对final提供的重排序规则如下:

1、当在constructor中对final变量写入之前,不会将构造对象的引用给出去。

2、读一个包含final域的对象的引用 happens before 后续对这个final域的读

 关于(1),是说当在constructor内包含对final的写时,虽然constructor不是原子的,但在JMM会在对final的写之后,在constructor return之前插入一条内存屏障,这样就保证了在constructor将对象给到另一个线程的时候,是一定能看见对final域的写的结果的。看代码:

public class FinalExample {
    int i;
    final int j;
    static FinalExample obj;

    // constructor
    public void FinalExample () {
        i = 1;
        j = 2;
    }

    public static void writer () {
        obj = new FinalExample();
    }

    public static void reader () {
        FinalExample object = obj;
        int a = object.i;
        int b = object.j;
    }

}  

现假设一个线程A执行writer方法,另一个线程B随后开始执行read方法。假设变量j不是final的,那么A在调用write时,constructor会发生两次写操作,这个时候线程B来调用reader读字段的时候,由于constructor方法不是原子的,又没有任何同步手段来协调线程A与B的同步,这时候就会乱掉了,比如constructor两次赋值发生了重排序,或者只完成了一次赋值,线程B就拿到构造一半的对象进行读值了,等等并发问题了。

但是,当把j变量定义为final时,就会发生一点点的变化了。

(1)final能够保证,在对象引用被任意线程可见之前,对象的final域一定被正确初始化了。而实现这种保证的手段,就是通过内存屏障,保证final域的赋值不会被重排序到constructor return之后。

(2)final能够保证,初次读对象引用于初次读该对象所包含的final域,不会发生重排序。所以在执行read方法时,final能够保证一定是先确实拿到了obj的引用之后,才能读obj中的final域j。

而实际上,IDEA对于final的赋值只能在final类变量定义时,不能将final的赋值推迟到constructor里。

posted @ 2017-04-27 09:16  Mr.do  阅读(236)  评论(0编辑  收藏  举报