多线程—在java中减少同步块的作用域会意外地损坏我的arraylist,为什么会这样?

cedebl8k  于 2021-06-27  发布在  Java
关注(0)|答案(1)|浏览(344)

有点晚了,我给你准备了圣诞特价。有个圣诞老人班 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 很好用。我真的不明白小号的问题 synchronized 前面显示的块。从我的观点来看,它仍然应该确保一个礼物不会多次分配给一个孩子,并且礼物数组列表上不应该同时有写(和读)操作。你能告诉我为什么我的假设是错的吗?

vaqhlq81

vaqhlq811#

这是因为代码包含竞争条件。让我们用下面的例子来说明竞争条件。
想象一下 Thread 1 阅读

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

它的评估结果是 true . 而 Thread 1 进入 synchronized 块,另一个线程(即。, Thread 2 )同时阅读

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

之前 Thread 1 有时间做 gotPresent.put(child, true); . 因此,上述 if 也计算为 true 为了 Thread 2 . Thread 1 是在 synchronized(this) 从礼物列表中删除礼物(即。, presents.remove(present); ). 现在 sizepresent 列表是 0 . Thread 1 退出 synchronized 阻止,而 Thread 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 例外。
然而,使整个方法同步工作得很好。
是的,因为

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

在前面的竞态条件示例中 Thread 2 会在

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

因为 Thread 1 ,在退出同步块之前

gotPresent.put(child, true);

到…的时候 Thread 2 会进入 synchronized 阻止以下语句

!gotPresent.get(child)

会被评估为 false ,因此 Thread 2 会在不打电话的情况下立即离开 int randomIndex = random.nextInt(presents.size()); 有一个大小的列表 0 .
因为您展示的方法是由多个线程并行执行的,所以您应该确保线程之间共享数据结构的互斥性,即 gotPresent 以及 presents . 例如,这意味着 containsKey , get ,和 put 应在同一同步块内执行。

相关问题