线程之间的并发问题

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对象,因此理论上得到一个mmyIntnull(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将使它们的分配成为原子.