java final域_java内存模型之-final域

1.1final域的重排序规则

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

1)在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用

变量,这两个操作之间不能重排序。

2)初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能

重排序。

1.1.1写final域重排序规则

写final域的重排序规则禁止把final域的写重排序到构造函数之外。这个规则的实现包含 下面2个方面。

1)JMM禁止编译器把final域的写重排序到构造函数之外。

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

写普通域的操作被编译器重排序到了构造函数之外,读线程B错误地读取了 普通变量i初始化之前的值。而写final域的操作,被写final域的重排序规则“限定”在了构造函数 之内,读线程B正确地读取了final变量初始化之后的值。

写final域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final域已经被 正确初始化过了,而普通域不具有这个保障。以上图为例,在读线程B“看到”对象引用obj时, 很可能obj对象还没有构造完成(对普通域i的写操作被重排序到构造函数外,此时初始值1还 没有写入普通域i)。

561640dab4f96beb2a4ed29d0e1e4349.png

1.1.2读final域重排序规则

读final域的重排序规则是,在一个线程中,初次读对象引用与初次读该对象包含的final 域,JMM禁止处理器重排序这两个操作(注意,这个规则仅仅针对处理器)。编译器会在读final 域操作的前面插入一个LoadLoad屏障。

1.2 final 域为引用类型

对于引用类型,写final域的重 排序规则对编译器和处理器增加了如下约束:在构造函数内对一个final引用的对象的成员域

的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。下面用样例程序解释一下:

public class FinalReferenceExample {

final int [] intArray; //final 是引用

static FinalReferenceExample obj;

public FinalReferenceExample (){ // 构造函数

intArray = new int[1]; //1

intArray[0] =1; //2

}

public static void writeOwo(){ //写线程A执行

obj = new FinalReferenceExample(); // 3

}

public static void writeTwo(){ //写线程A执行

obj.intArray[0] = 2; // 3

}

public static void reader(){ //写线程B执行

if(obj != null){ // 4

int temp1 = obj.intArray[0];

}

}

}

1是对final域的写入,2是对这个final域引用的对象的成员域的写入,3是把被 构造的对象的引用赋值给某个引用变量。这里除了前面提到的1不能和3重排序外,2和3也不能重排序。

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

public class FinalReferenceEsacapeExample {

final int i;

static FinalReferenceEsacapeExample obj;

public FinalReferenceEsacapeExample(){

i = 1; //1

obj = this;           //2

}

public static void writer(){

new FinalReferenceEsacapeExample();

}

public static void reader(){

if(obj != null){

int temp = obj.i;

}

}

}

a6a0956768529ac50c3bcc39011de1e5.png

假设一个线程A执行writer()方法,另一个线程B执行reader()方法。这里的操作2使得对象 还未完成构造前就为线程B可见。即使这里的操作2是构造函数的最后一步,且在程序中操作2 排在操作1后面,执行read()方法的线程仍然可能无法看到final域被初始化后的值,因为这里的操作1和操作2之间可能被重排序

f26cb00ce3cb7a3db130d72241d8df1c.png

从图3-32可以看出:在构造函数返回前,被构造对象的引用不能为其他线程所见,因为此 时的final域可能还没有被初始化。在构造函数返回后,任意线程都将保证能看到final域正确初始化之后的值。

1.4final语义在处理器中的实现

上面我们提到,写final域的重排序规则会要求编译器在final域的写之后,构造函数return 之前插入一个StoreStore障屏。读final域的重排序规则要求编译器在读final域的操作前面插入 一个LoadLoad屏障。由于X86处理器不会对写-写操作做重排序,所以在X86处理器中,写final域需要的 StoreStore障屏会被省略掉。同样,由于X86处理器不会对存在间接依赖关系的操作做重排序, 所以在X86处理器中,读final域需要的LoadLoad屏障也会被省略掉。也就是说,在X86处理器 中,final域的读/写不会插入任何内存屏障!

JSR-133专家组增强了final的语义。通过为final域增加写和读重排序 规则,可以为Java程序员提供初始化安全保证:只要对象是正确构造的(被构造对象的引用在 构造函数中没有“逸出”),那么不需要使用同步(指lock和volatile的使用)就可以保证任意线程 都能看到这个final域在构造函数中被初始化之后的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值