我正在开发一个应用程序,它使用 HashMap
来共享状态。我需要通过单元测试来证明它在多线程环境下会有问题。
我尝试通过检查两者中 HashMap
的大小和元素来检查单线程环境和多线程环境中应用程序的状态。但这似乎无济于事,状态始终相同。
是否有任何其他方法可以证明或证明对 map 执行操作的应用程序可以很好地处理并发请求?
最佳答案
这很容易证明。
不久
HashMap 基于数组,其中每个项目代表一个桶。随着更多键的添加,桶会增长,并且在某个阈值时,数组会被重新创建,并具有更大的大小,以便其桶分布得更均匀(性能考虑)。在数组重新创建期间,数组变为空,这会导致调用方的结果为空,直到重新创建完成。
细节与证明
这意味着有时 HashMap#put()
会在内部调用 HashMap#resize()
以使底层数组变大。
HashMap#resize()
为 table
字段分配一个容量更大的新空数组,并用旧项目填充它。虽然发生了这种填充,但底层数组不包含所有旧项目并且使用现有键调用 HashMap#get()
可能会返回 null
.
下面的代码演示了这一点。您很可能会遇到异常,这意味着 HashMap
不是线程安全的。我将目标键选择为 65 535
- 这样它将成为数组中的最后一个元素,因此成为重新填充期间的最后一个元素,这增加了获得 null
的可能性> 关于 HashMap#get()
(要了解原因,请参阅 HashMap#put()
实现)。
final Map<Integer, String> map = new HashMap<>();
final Integer targetKey = 0b1111_1111_1111_1111; // 65 535
final String targetValue = "v";
map.put(targetKey, targetValue);
new Thread(() -> {
IntStream.range(0, targetKey).forEach(key -> map.put(key, "someValue"));
}).start();
while (true) {
if (!targetValue.equals(map.get(targetKey))) {
throw new RuntimeException("HashMap is not thread safe.");
}
}
一个线程向 map 添加新键。另一个线程不断检查 targetKey
是否存在。
如果算上这些异常,我得到大约 200 000
。
关于java - 如何证明java中的HashMap不是线程安全的,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18542037/