减少Java中同步块的范围会意外地损坏我的ArrayList,为什么会这样?
有点晚了,我有一个圣诞特价给你。有一个圣诞老人类,它有一个礼物的减少Java中同步块的范围会意外地损坏我的ArrayList,为什么会这样?,java,multithreading,parallel-processing,synchronization,thread-safety,Java,Multithreading,Parallel Processing,Synchronization,Thread Safety,有点晚了,我有一个圣诞特价给你。有一个圣诞老人类,它有一个礼物的ArrayList和一个Map来跟踪哪些孩子已经收到了礼物。孩子们模仿成线,不断地同时向圣诞老人要礼物。为简单起见,每个孩子只收到一份(随机)礼物 Santa类中的方法偶尔会产生一个IllegalArgumentException,因为presents.size()是负数 public Present givePresent(Child child) { if(gotPresent.containsKey(child
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;
}
但是,使整个方法
同步化
效果很好。我不太理解前面显示的较小的synchronized
块的问题。从我的观点来看,它仍然应该确保礼物不会多次分配给孩子,并且礼物数组列表上不应该有并发写入(和读取)。你能告诉我为什么我的假设是错误的吗?发生这种情况是因为代码中包含了竞争条件。让我们用下面的例子来说明竞争条件
假设线程1
读取
`if(gotPresent.containsKey(child) && !gotPresent.get(child))`
它的计算结果为true
。当thread1
进入synchronized
块时,另一个线程(即thread2
)也读取
if(gotPresent.containsKey(child) && !gotPresent.get(child))
在之前,线程1
有时间执行gotPresent.put(child,true)代码>。因此,前面提到的if
对于线程2
也计算为true
线程1
位于同步(this)
中,并从礼物列表中删除礼物(即礼物。删除(礼物);
)。现在,当前
列表的大小
为0
<代码>线程1
退出同步
块,而线程2
只是进入它,并最终调用
int randomIndex = random.nextInt(presents.size());
由于presents.size()
将返回0
,因此random.nextInt
实现如下:
public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
...
}
在上述竞态条件示例中,线程2
将在
if(gotPresent.containsKey(child) && !gotPresent.get(child))
因为线程1
,在退出synchronized块之前,应该已经完成了
gotPresent.put(child, true);
当线程2
进入已同步
块时,将执行以下语句
!gotPresent.get(child)
将计算为false
,因此线程2
将在不调用int randomIndex=random.nextInt(presents.size())的情况下立即退出代码>带有大小列表0
由于所显示的方法由多个线程并行执行,因此应确保线程之间共享数据结构的互斥,即gotPresent
和presents
。这意味着,例如,像containsKey
、get
和put
这样的操作应该在同一个同步块中执行。现在完全清楚了,非常感谢。谢谢,确保这些孩子得到礼物:D
!gotPresent.get(child)