gpt4 book ai didi

java - 减少 Java 中同步块(synchronized block)的范围意外地损坏了我的 ArrayList,为什么会出现这种情况?

转载 作者:行者123 更新时间:2023-12-03 12:48:48 25 4
gpt4 key购买 nike

有点晚了,我为你准备了一份圣诞特别礼物。有一个圣诞老人类,其中包含礼物的 ArrayList 和用于跟踪哪些 child 已经收到礼物的 Map。 children 被塑造成线程,不断地同时向圣诞老人索要礼物。为简单起见,每个 child 都会收到一份(随机)礼物。

这里是 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 block)的问题。从我的角度来看,它仍然应该确保礼物不会多次分配给 child ,并且礼物 ArrayList 上不应该有并发写入(和读取)。你能告诉我为什么我的假设是错误的吗?

最佳答案

发生这种情况是因为代码包含竞争条件。让我们使用以下示例来说明竞争条件。

假设线程 1 读取

`if(gotPresent.containsKey(child) && !gotPresent.get(child))` 

其计算结果为true。当线程 1 进入synchronized block 时,另一个线程(线程 2)也会读取

if(gotPresent.containsKey(child) && !gotPresent.get(child)) 

线程 1 有时间执行 gotPresent.put(child, true); 之前。因此,对于线程 2,上述 if 的计算结果也为 true

线程 1 位于 synchronized(this) 内,并从呈现列表中删除呈现(即, presents.remove(present);)。现在present列表的size0线程 1 退出 synchronized block ,而线程 2 刚刚进入它,并最终调用

int randomIndex = random.nextInt(presents.size()); 

因为presents.size()会返回0,而random.nextInt实现如下:

  public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
...
}

您收到 IllegalArgumentException 异常。

However, making the whole method synchronized works just fine.

是的,因为

 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;
}
}

在上述竞争条件示例中,线程 2 会在

之前等待
if(gotPresent.containsKey(child) && !gotPresent.get(child))

因为线程 1 在退出同步块(synchronized block)之前会完成

gotPresent.put(child, true);

线程2进入synchronized block 时,执行以下语句

!gotPresent.get(child)

将被评估为false,因此Thread 2将立即退出而不调用int randomIndex = random.nextInt(presents.size()); ,列表大小为 0

由于您所展示的方法是由多个线程并行执行的,因此您应该确保线程之间共享数据结构的互斥,即 gotPresentpresents。例如,这意味着 containsKeygetput 等操作应该在同一个同步块(synchronized block)中执行。

关于java - 减少 Java 中同步块(synchronized block)的范围意外地损坏了我的 ArrayList,为什么会出现这种情况?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65494593/

25 4 0