use*_*239 1 java concurrency null multithreading
假设我有一个具有原始值的实例变量:
Integer mMyInt = 1;
Run Code Online (Sandbox Code Playgroud)
有两个主题.
第一个通过调用mMyInt来改变:
void setInt() {
mMyInt = 2;
}
Run Code Online (Sandbox Code Playgroud)
第二个线程通过调用获得mMyInt:
Integer getInt() {
return mMyInt;
}
Run Code Online (Sandbox Code Playgroud)
两个线程都不使用同步.
我的问题是,第二个线程可以从getInt()得到什么值?可以只有1还是2?可以变空吗?
谢谢
Enn*_*oji 10
编辑:感谢@irreputable的重要更新.
除非在构造期间对象已经转义(见下文),否则赋值mMyInt=1在任何访问getter/setter之前发生.同样在java中,对象赋值是原子的(你有可能观察到一些无效的地址分配.小心因为64位原语赋值,例如double并且long不是原子的).
因此,在这种情况下,可能的值为1或2.
在这种情况下,对象可以在构造期间逃脱:
class Escape {
Integer mmyInt = 1;
Escape(){
new Thread(){
public void run(){
System.out.println(Escape.this.mmyInt);
}
}.start();
}
}
Run Code Online (Sandbox Code Playgroud)
虽然在实践中它可能很少发生,但在上面的例子中,新线程可以观察到一个未完全构造的Escape对象,因此理论上得到一个mmyInt值null(AFAIK你仍然不会得到一些随机的内存位置).
如果它是HashMap对象怎么办?实例变量mMyMap具有原始值.然后,第一个线程调用"mMyMap = new HashMap();" 第二个线程调用"return mMyMap;" 第二个线程可以为null,还是只能获取原始或新的HashMap对象?
当"对象引用赋值是原子的"时,意味着您不会观察到中间赋值.它可以是之前的值,也可以是之后的值.因此,如果发生的唯一分配是map = someNonNullMap();在构造完成之后(并且在构造期间字段被指定了非空值)并且在构造期间对象没有被转义,则无法观察null.
更新: 我咨询了一个并发专家,据他说,Java内存模型允许编译器重新排序赋值和对象构造(实际上我认为这是非常不可能的).
因此,例如在下面的例子中,thread1可以分配一些堆,为其分配一些值map,继续构造map.同时,thread2来观察一个部分构造的对象.
class Clever {
Map map;
Map getMap(){
if(map==null){
map = deriveMap(); }
return map;
}
}
Run Code Online (Sandbox Code Playgroud)
JDK在String类中有一个类似的构造(不是确切的引用):
class String {
int hashCode = 0;
public int hashCode(){
if(hashCode==0){
hashCode = deriveHashCode();
}
return hashCode;
}
}
Run Code Online (Sandbox Code Playgroud)
根据相同的并发专家的说法,这可行,因为非易失性缓存是原始的而不是对象.
通过引入关系之前的事件可以避免这些问题.在上述情况中,可以通过声明成员来做到这一点volatile.同样对于64位原语,声明它们volatile将使它们的分配成为原子.
| 归档时间: |
|
| 查看次数: |
247 次 |
| 最近记录: |