cha*_*tle 3 java concurrency multithreading synchronization memory-model
采取以下java代码:
public class SomeClass {
private boolean initialized = false;
private final List<String> someList;
public SomeClass() {
someList = new ConcurrentLinkedQueue<String>();
}
public void doSomeProcessing() {
// do some stuff...
// check if the list has been initialized
if (!initialized) {
synchronized(this) {
if (!initialized) {
// invoke a webservice that takes a lot of time
final List<String> wsResult = invokeWebService();
someList.addAll(wsResult);
initialized = true;
}
}
}
// list is initialized
for (final String s : someList) {
// do more stuff...
}
}
}
Run Code Online (Sandbox Code Playgroud)
诀窍是doSomeProcessing只在某些条件下调用.初始化列表是一个非常昂贵的过程,可能根本不需要它.
我读过有关为什么双重检查成语被打破的文章,当我看到这段代码时我有点怀疑.但是,这个例子中的控制变量是一个布尔值,因此我需要一个简单的写指令.
另外,请注意someList已声明为final并且保留对并发列表的引用,该列表的writes 发生在之前 reads ; 如果不是的ConcurrentLinkedQueue列表是一个简单ArrayList或者LinkedList,即使它已被宣布为final,在writes不要求发生,之前的reads.
那么,上面给出的代码是否没有数据竞争?
好的,让我们来获取Java语言规范.第17.4.5节定义发生在 - 之前如下:
可以通过先发生关系来排序两个动作.如果一个动作发生在另一个动作之前,则第一个动作在第二个动作之前可见并在第二个之前被命 如果我们有两个动作x和y,我们写hb(x,y)来表示x发生在y之前.
- 如果x和y是同一个线程的动作,并且x在程序顺序中出现在y之前,那么hb(x,y).
- 有一个之前发生从物体到该对象的终结器(§12.6)的开始的构造的端部边缘.
- 如果动作x与后续动作y同步,那么我们也有hb(x,y).
- 如果是hb(x,y)和hb(y,z),那么hb(x,z).
应该注意的是,两个动作之间存在的先发生关系并不一定意味着它们必须在实现中以该顺序发生.如果重新排序产生的结果与合法执行一致,则不是非法的.
然后继续讨论两个:
更具体地说,如果两个动作共享发生在之前的关系,则它们不一定必须按照该顺序发生在它们不与之共享的任何代码之间.例如,在具有另一个线程中的读取的数据争用中的一个线程中的写入可能看起来不按顺序发生到那些读取.
在您的实例中,线程检查
if (!initialized)
Run Code Online (Sandbox Code Playgroud)
initialized在看到添加到其中的所有写入someList并因此使用部分填充列表之前,可能会看到新值.
请注意你的论点
另外,请注意
someList已声明为final并且保留对并发列表的引用,该列表的writes发生在之前reads
是无关紧要的.是的,如果线程从列表中读取一个值,我们可以得出结论,他也会在写入该值之前看到发生的任何事情.但如果它没有读取值呢?如果列表显示为空,该怎么办?即使它读取了值,也不意味着后续写入已经执行,因此列表可能看起来不完整.