线程间并发问题

发布于 2024-09-10 17:35:43 字数 356 浏览 5 评论 0原文

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

Integer mMyInt = 1;

有两个线程。

第一个线程通过调用更改 mMyInt:

void setInt() {
    mMyInt = 2;
}

第二个线程通过调用获取 mMyInt:

Integer getInt() {
  return mMyInt;
}

两个线程都不使用同步。

我的问题是,第二个线程可以从 getInt() 获得的可能值是多少?只能是1个或者2个吗?可以归零吗?

谢谢

Suppose I have a instance variable that has original value:

Integer mMyInt = 1;

There are two threads.

The first changes mMyInt by calling:

void setInt() {
    mMyInt = 2;
}

The second thread gets mMyInt by calling:

Integer getInt() {
  return mMyInt;
}

Both threads don't use synchronization.

My questions is, what is the possible value the the second thread can get from getInt()? Can it be only 1 or 2? Can it get null?

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

在巴黎塔顶看东京樱花 2024-09-17 17:35:44

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

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

因此,在这种情况下,可能的值为 1 或 2。在这种情况下,

对象可以在构造过程中逃逸:

 class Escape {
    Integer mmyInt = 1;

    Escape(){
        new Thread(){
            public void run(){
                System.out.println(Escape.this.mmyInt);
            }
        }.start();
    }
 }

尽管实际上这种情况很少发生,但在上述情况下,新线程可以观察到未完全构造的 转义 对象,从而理论上得到 mmyInt 值为 null (AFAIK 你仍然不会得到一些随机内存位置)。

如果是HashMap怎么办
目的?实例变量 mMyMap
具有原始价值。然后,第一个
线程调用“mMyMap = new HashMap();”
第二个线程调用“return
mMyMap;" 第二个线程能否获取到
null,或者只能获取原始的或
新的 HashMap 对象?

当“对象引用分配是原子的”时,这意味着您将不会观察到中间分配。它要么是之前的值,要么是之后的值。因此,如果在构造完成后发生的唯一分配是 map = someNonNullMap(); (并且在构造期间为该字段分配了非空值)并且该对象在构造期间尚未转义,您无法观察 null

更新:
我咨询了一位并发专家,根据他的说法,Java 内存模型允许编译器重新排序分配和对象构造(但实际上我认为这不太可能)。

因此,例如在下面的情况下,thread1可以分配一些堆,为map分配一些值,然后继续构造map。与此同时,线程 2 来观察一个部分构造的对象。

class Clever {
   Map map;

   Map getMap(){
       if(map==null){
           map = deriveMap();        }
       return map;
   }
}

JDK 在 String 类中具有类似的构造(不完全引用):

class String {
   int hashCode = 0;

   public int hashCode(){
       if(hashCode==0){
           hashCode = deriveHashCode();
    }
       return hashCode;
   }
}

根据同一并发专家的说法,这确实有效,因为非易失性缓存是原始的而不是对象。

通过引入发生在关系之前可以避免这些问题。在上述情况下,可以通过声明成员易失性来做到这一点。同样对于 64 位原语,声明它们 易失性 将使它们的分配成为原子的。

EDIT: Important update thanks to @irreputable.

Unless the object has escaped during construction (see below), the assignment mMyInt=1 happens before any access to the getter/setter. Also in java, object assignment is atomic (there is 0 chance that you observe some invalid address assigned. Be careful because 64bit primitive assignments, such as double and long are NOT atomic).

So, in that case the possible value is either 1 or 2.

Object can escape during construction in this kind of situation:

 class Escape {
    Integer mmyInt = 1;

    Escape(){
        new Thread(){
            public void run(){
                System.out.println(Escape.this.mmyInt);
            }
        }.start();
    }
 }

Although in practice it probably rarely happens, in the above case, the new thread can observe an not fully constructed Escape object and thus in theory get an mmyInt value of null (AFAIK you still won't get some random memory location).

What if it it is a HashMap
object? The instance variable mMyMap
has original value. Then, the first
thread calls "mMyMap = new HashMap();"
The second thread calls "return
mMyMap;" Can the second thread get
null, or can it only get original or
new HashMap object?

When "Object reference assignment is atomic", it means that you will NOT observe an intermediate assignment. It's either the value before, or the value after. So if the only assignment that is happening is map = someNonNullMap(); after the construction has completed (and the field was assigned a non null value during the construction) and the object has not escaped during the construction, you can't observe null.

Update:
I consulted a concurrency expert, and according to him, the Java Memory Model allows compilers to reorder assignment and object construction (while in practice I imagine that would be highly unlikely).

So for example in the below case, thread1 can allocate some heap, assign some value to map, the continue construction of map. Meanwhile thread2 comes and observe an partially constructed object.

class Clever {
   Map map;

   Map getMap(){
       if(map==null){
           map = deriveMap();        }
       return map;
   }
}

JDK has a similar construct in the String class (not exact quote):

class String {
   int hashCode = 0;

   public int hashCode(){
       if(hashCode==0){
           hashCode = deriveHashCode();
    }
       return hashCode;
   }
}

This DOES work because the non-volatile cache is primitive and not an object, according to the same concurrency experts.

These problems can be avoided by introducing an happens before relationship. In the cases above, one could do this by declaring the members volatile. Also for 64bit primitive, declaring them volatile will make their assignment atomic.

何时共饮酒 2024-09-17 17:35:44
// somewhere
static YourClass obj;

//thread 1
obj = new YourClass();

// thread 2
if(obj!=null)
    obj.getInt();

理论上,线程 2 可以获得 null。

// somewhere
static YourClass obj;

//thread 1
obj = new YourClass();

// thread 2
if(obj!=null)
    obj.getInt();

theoretically, thread 2 could get a null.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文