假设我有一个具有原始值的实例变量:
Integer mMyInt = 1;
有两个主题。
第一个通过调用:
来更改mMyIntvoid setInt() {
mMyInt = 2;
}
第二个线程通过调用:
获得mMyIntInteger getInt() {
return mMyInt;
}
两个线程都不使用同步。
我的问题是,第二个线程可以从getInt()得到什么值?可以只有1还是2?它可以变为空吗?
由于
答案 0 :(得分: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();
}
}
虽然在实践中它可能很少发生,但在上面的例子中,新线程可以观察到一个未完全构造的Escape
对象,因此在理论上得到mmyInt
null
的值( AFAIK你仍然不会得到一些随机的内存位置)。
如果它是HashMap怎么办? 宾语?实例变量mMyMap 有原始价值。然后,第一个 线程调用“mMyMap = new HashMap();” 第二个线程称为“返回 mMyMap;“第二个线程可以得到 null,或者它只能获得原始或 新的HashMap对象?
当“对象引用分配是原子的”时,意味着您不会观察到中间分配。它可以是之前的值,也可以是之后的值。因此,如果在构造完成之后发生的唯一分配是map = someNonNullMap();
(并且在构造期间字段被指定了非空值)并且在构造期间对象没有被转义,则无法观察{{ 1}}。
<强>更新强> 我咨询了一个并发专家,据他说,Java内存模型允许编译器重新排序赋值和对象构造(实际上我认为这是非常不可能的)。
例如,在下面的例子中,thread1可以分配一些堆,为null
分配一些值,继续构造map
。同时,thread2来观察一个部分构造的对象。
map
JDK在class Clever {
Map map;
Map getMap(){
if(map==null){
map = deriveMap(); }
return map;
}
}
类中有一个类似的构造(不是确切的引用):
String
根据相同的并发专家的说法,这可行,因为非易失性缓存是原始的而不是对象。
通过引入关系之前的事件可以避免这些问题。在上述情况中,可以通过声明成员class String {
int hashCode = 0;
public int hashCode(){
if(hashCode==0){
hashCode = deriveHashCode();
}
return hashCode;
}
}
来实现此目的。同样对于64位原语,声明它们volatile
将使它们的分配成为原子。
答案 1 :(得分:0)
// somewhere
static YourClass obj;
//thread 1
obj = new YourClass();
// thread 2
if(obj!=null)
obj.getInt();
从理论上讲,线程2可以得到一个空值。