ran*_*321 2 java parallel-processing multithreading synchronization thread-safety
有点晚了,我有一个圣诞节特别给你。有一个圣诞老人班,里面有一个ArrayList礼物和一个Map跟踪哪些孩子已经收到了他们的礼物。孩子们模仿成线程不断地同时向圣诞老人索要礼物。为简单起见,每个孩子都收到一份(随机)礼物。
这是 Santa 类中的方法偶尔会产生一个IllegalArgumentException因为presents.size()是否定的。
public Present givePresent(Child child) {
if(gotPresent.containsKey(child) && !gotPresent.get(child)) {
synchronized(this) {
gotPresent.put(child, true);
Random random = new Random();
int randomIndex = random.nextInt(presents.size());
Present present = presents.get(randomIndex);
presents.remove(present);
return present;
}
}
return null;
}
Run Code Online (Sandbox Code Playgroud)
但是,使整个方法synchronized工作得很好。我真的不明白synchronized之前显示的较小尺寸的块的问题。从我的角度来看,它仍然应该确保礼物不会多次分配给孩子,并且礼物 ArrayList 上不应该有并发写入(和读取)。你能告诉我为什么我的假设是错误的吗?
发生这种情况是因为代码包含竞争条件。让我们用下面的例子来说明这种竞争条件。
想象一下,Thread 1读
`if(gotPresent.containsKey(child) && !gotPresent.get(child))`
Run Code Online (Sandbox Code Playgroud)
它评估为true. 当Thread 1进入synchronized块时,另一个线程(即, Thread 2)也读取
if(gotPresent.containsKey(child) && !gotPresent.get(child))
Run Code Online (Sandbox Code Playgroud)
之前Thread 1一直有时间做gotPresent.put(child, true);。因此,上述if也评估true为Thread 2。
Thread 1位于 内synchronized(this)并从礼物列表中删除礼物(即, presents.remove(present);)。现在size的的present名单0。Thread 1退出synchronized块,而Thread 2刚刚进入它,并最终调用
int randomIndex = random.nextInt(presents.size());
Run Code Online (Sandbox Code Playgroud)
因为presents.size()会返回0,random.nextInt实现如下:
public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
...
}
Run Code Online (Sandbox Code Playgroud)
你得到了IllegalArgumentException例外。
但是,使整个方法同步就可以了。
是的,因为与
synchronized(this) {
if(gotPresent.containsKey(child) && !gotPresent.get(child)) {
gotPresent.put(child, true);
Random random = new Random();
int randomIndex = random.nextInt(presents.size());
Present present = presents.get(randomIndex);
presents.remove(present);
return present;
}
}
Run Code Online (Sandbox Code Playgroud)
在前面提到的竞争条件示例中,Thread 2会在
if(gotPresent.containsKey(child) && !gotPresent.get(child))
Run Code Online (Sandbox Code Playgroud)
并且因为Thread 1,在退出同步块之前,会做
gotPresent.put(child, true);
Run Code Online (Sandbox Code Playgroud)
到时候Thread 2会进入synchronized块下面的语句
!gotPresent.get(child)
Run Code Online (Sandbox Code Playgroud)
将评估为false,因此Thread 2将立即退出而不调用 int randomIndex = random.nextInt(presents.size());大小列表0。
由于您展示的方法是由多个线程并行执行的,因此您应该确保线程之间共享数据结构的互斥,即gotPresent和presents。这意味着,例如,该操作一样containsKey,get和put应该在相同的synchronized块内执行。