您的位置:首页 > 消费 > 正文

并发编程:final关键字

2023-06-28 08:37:44 来源:日拱一卒程序猿

一、构造方法溢出问题

考虑下面的代码:

num3和num4的值是否一定是1和2? num3、num4不见得一定等于1,2。

和DCL的例子类似,也就是构造方法溢出问题。 myClass = new MyClass()这行代码,分解成三个操作: 1. 分配一块内存; 2. 在内存上初始化i=1,j=2; 3. 把myClass指向这块内存。 操作2和操作3可能重排序,因此线程B可能看到未正确初始化的值。对于构造方法溢出,就是一个 对象的构造并不是“原子的”,当一个线程正在构造对象时,另外一个线程却可以读到未构造好的“一半对 象”。


(资料图片仅供参考)

二、final的happen-before语义

要解决这个问题,不止有一种办法。

办法1:给num1,num2加上volatile关键字。

办法2:为read/write方法都加上synchronized关键字。

如果num1,num2只需要初始化一次,还可以使用final关键字。

之所以能解决问题,是因为同volatile一样,final关键字也有相应的happen-before语义:

1. 对final域的写(构造方法内部),happen-before于后续对final域所在对象的读。

2. 对final域所在对象的读,happen-before于后续对final域的读。 通过这种happen-before语义的限定,保证了final域的赋值,一定在构造方法之前完成,不会出现 另外一个线程读取到了对象,但对象里面的变量却还没有初始化的情形,避免出现构造方法溢出的问题。

三、happen-before规则总结

1. 单线程中的每个操作,happen-before于该线程中任意后续操作。

2. 对volatile变量的写,happen-before于后续对这个变量的读。

3. 对synchronized的解锁,happen-before于后续对这个锁的加锁。

4. 对final变量的写,happen-before于final域对象的读,happen-before于后续对final变量的 读。

四个基本规则再加上happen-before的传递性,就构成JMM对开发者的整个承诺。在这个承诺以外 的部分,程序都可能被重排序,都需要开发者小心地处理内存可见性问题。

标签:

相关阅读

大家爱看

“内部指标”?“计划外招生”?“军校招生”诈骗要警惕 “内部指标”?“计划外招生”?“军校招生”诈骗要警惕

“内部指标”上军校?百分之百是骗局!

最近更新