不是线程安全的对象发布 [英] Not thread-safe Object publishing
问题描述
在阅读"Java并发实践"时,第3.5节中有此部分:
Reading "Java Concurrency In Practice", there's this part in section 3.5:
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
除了创建两个Holder
实例的明显的线程安全隐患外,该书还声称可能会发生发布问题.
Besides the obvious thread safety hazard of creating two instances of Holder
, the book claims a possible publishing issue can occur.
此外,对于Holder
类,例如
public Holder {
int n;
public Holder(int n) { this.n = n };
public void assertSanity() {
if(n != n)
throw new AssertionError("This statement is false.");
}
}
可以抛出AssertionError
!
这怎么可能?我能想到的唯一允许这种荒谬行为的方法是,如果Holder
构造函数不会被阻塞,那么当构造函数代码仍在另一个线程中运行时,将创建对实例的引用.
How is this possible? The only way I can think of that can allow such ridiculous behavior is if the Holder
constructor would not be blocking, so a reference would be created to the instance while the constructor code still runs in a different thread.
这可能吗?
推荐答案
之所以可行,是因为Java的内存模型较弱.它不保证读写顺序.
The reason why this is possible is that Java has a weak memory model. It does not guarantee ordering of read and writes.
这个特殊的问题可以通过以下代表两个线程的两个代码片段来重现.
This particular problem can be reproduced with the following two code snippets representing two threads.
线程1:
someStaticVariable = new Holder(42);
线程2:
someStaticVariable.assertSanity(); // can throw
表面上似乎不可能发生这种情况.为了理解为什么会发生这种情况,您必须克服Java语法,然后再降到更低的水平.如果您看一下线程1的代码,则基本上可以将其分解为一系列的内存写和分配:
On the surface it seems impossible that this could ever occur. In order to understand why this can happen, you have to get past the Java syntax and get down to a much lower level. If you look at the code for thread 1, it can essentially be broken down into a series of memory writes and allocations:
- 分配给指针1的内存
- 将42写入指针1的偏移量为0
- 将指针1写入someStaticVariable
由于Java的内存模型较弱,因此从线程2的角度来看,代码完全有可能按以下顺序实际执行:
Because Java has a weak memory model, it is perfectly possible for the code to actually execute in the following order from the perspective of thread 2:
- 分配给指针1的内存
- 将指针1写入someStaticVariable
- 将42写入指针1的偏移量为0
吓人?是的,但是有可能发生.
Scary? Yes but it can happen.
这意味着线程2现在可以在n
获得值42之前调用assertSanity
.在assertSanity
期间,有可能在操作#之前两次读取值n
. 3完成并在此之后执行一次,因此会看到两个不同的值并引发异常.
What this means though is that thread 2 can now call into assertSanity
before n
has gotten the value 42. It is possible for the value n
to be read twice during assertSanity
, once before operation #3 completes and once after and hence see two different values and throw an exception.
编辑
根据乔恩·斯基特(Jon Skeet),AssertionError
可能仍然发生 Java 8 除非字段是最终的.
According to Jon Skeet, the AssertionError
migh still occur with Java 8 unless the field is final.
这篇关于不是线程安全的对象发布的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!