线程之间的并发问题

时间:2010-07-17 05:43:42

标签: java multithreading concurrency null

假设我有一个具有原始值的实例变量:

Integer mMyInt = 1;

有两个主题。

第一个通过调用:

来更改mMyInt
void setInt() {
    mMyInt = 2;
}

第二个线程通过调用:

获得mMyInt
Integer getInt() {
  return mMyInt;
}

两个线程都不使用同步。

我的问题是,第二个线程可以从getInt()得到什么值?可以只有1还是2?它可以变为空吗?

由于

2 个答案:

答案 0 :(得分:10)

编辑:感谢@irreputable重要更新。

除非对象在构造期间被转义(见下文),否则赋值mMyInt=1在任何访问getter / setter之前发生。同样在java中,对象赋值是原子的(你有可能观察到一些无效的地址分配。要小心,因为64位原始赋值,例如doublelong不是原子的)。

因此,在这种情况下,可能的值为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可以得到一个空值。