Java HashSet似乎包含重复值

nwo49xxi  于 2023-01-19  发布在  Java
关注(0)|答案(1)|浏览(147)

我有一个单元测试失败了大约30次中的1次,我不明白为什么。下面是它的简化版本:

@Test
void size() {
    int totalIndexes = 10;
    Set<Integer> usedIndexes = new HashSet<>();
    AtomicInteger index = new AtomicInteger();
    Stream.generate(() -> index.getAndIncrement() % totalIndexes).parallel().limit(1000).forEach(i -> {
        try {
            Thread.sleep(1);
        } catch (InterruptedException ignore) {}
        usedIndexes.add(i);
    });
    if (usedIndexes.size() > totalIndexes) {
        // Should be impossible?
        System.out.println(usedIndexes.stream().map(i -> String.format("%s %d", i, i.hashCode()))
                .collect(Collectors.joining(" | ")));
        System.out.println("Total: " + usedIndexes.size());
    }
    assertTrue(usedIndexes.size() <= totalIndexes);
}

睡眠只是为了确保使用了几个线程,因为它似乎与问题有关(不确定)。如果没有它,大约30K运行后就会出现问题。
如您所见,我添加了坏情况的打印,并获得了一些示例输出,如:

0 0 | 1 1 | 2 2 | 3 3 | 4 4 | 5 5 | 6 6 | 7 7 | 8 8 | 9 9
Total: 26

8 8 | 0 0 | 9 9 | 1 1 | 2 2 | 3 3 | 4 4 | 5 5 | 6 6 | 7 7 | 4 4 | 5 5 | 6 6 | 7 7
Total: 16

断点截图:

此外,不信任我写Java的多年经验,我决定向ChatGPT询问:
我:
考虑以下测试:usedIndexes可以大于10个对象吗?怎么做?[以上代码]
聊天:
usedIndexes的值应始终小于或等于totalIndexes,因为流将生成介于0和totalIndexes-1之间的整数(包含),方法是使用模运算符%和getAndIncrement()方法。然后,流被限制为1000个元素,因此它不会生成超过1000个整数。由于usedIndexes集只存储唯一整数,其大小不应大于totalIndexes。
如果生成的每个数字都是唯一的,那么usedIndexes可能等于totalIndexes,如果生成的一些数字相同,那么usedIndexes也可能小于totalIndexes,这是因为HashSet只存储唯一的元素,集合的大小将是唯一元素的数量。
我一定错过了一些简单的东西。使用最新的IntelliJ,Java 17。

ufj5ltwl

ufj5ltwl1#

HashSet不支持并发访问。请改用ConcurrentHashMap.newKeySet()
来自HashSet的文档:

**请注意,此实现不同步。**如果多个线程并发访问哈希集,并且至少有一个线程修改了该集,则必须进行外部同步。

当您从一个 parallel 流的终端操作中添加元素时,您违反了HashSet的约定,因此遇到了您所描述的不一致的结果。

相关问题