线程间并发问题
假设我有一个具有原始值的实例变量:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
编辑:感谢@irreputable 的重要更新。
除非对象在构造过程中转义(见下文),否则赋值
mMyInt=1
发生在对 getter/setter 的任何访问之前。同样在 java 中,对象分配是原子的(您观察到分配的某些无效地址的可能性为 0。要小心,因为 64 位基元分配,例如double
和long
不是原子的)。因此,在这种情况下,可能的值为 1 或 2。在这种情况下,
对象可以在构造过程中逃逸:
尽管实际上这种情况很少发生,但在上述情况下,新线程可以观察到未完全构造的
转义
对象,从而理论上得到mmyInt
值为null
(AFAIK 你仍然不会得到一些随机内存位置)。当“对象引用分配是原子的”时,这意味着您将不会观察到中间分配。它要么是之前的值,要么是之后的值。因此,如果在构造完成后发生的唯一分配是
map = someNonNullMap();
(并且在构造期间为该字段分配了非空值)并且该对象在构造期间尚未转义,您无法观察null
。更新:
我咨询了一位并发专家,根据他的说法,Java 内存模型允许编译器重新排序分配和对象构造(但实际上我认为这不太可能)。
因此,例如在下面的情况下,thread1可以分配一些堆,为
map
分配一些值,然后继续构造map
。与此同时,线程 2 来观察一个部分构造的对象。JDK 在
String
类中具有类似的构造(不完全引用):根据同一并发专家的说法,这确实有效,因为非易失性缓存是原始的而不是对象。
通过引入发生在关系之前可以避免这些问题。在上述情况下,可以通过声明成员
易失性
来做到这一点。同样对于 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 asdouble
andlong
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:
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 anmmyInt
value ofnull
(AFAIK you still won't get some random memory location).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 observenull
.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 ofmap
. Meanwhile thread2 comes and observe an partially constructed object.JDK has a similar construct in the
String
class (not exact quote):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 themvolatile
will make their assignment atomic.理论上,线程 2 可以获得 null。
theoretically, thread 2 could get a null.