HashMap是线程不安全的原因
1. 并发修改异常
同时进行put操作时可能造成元素的丢失
假设有两个线程 A 和 B,它们尝试同时向 HashMap 中添加元素。
HashMap<Integer, String> map = new HashMap<>();
Thread a = new Thread(() -> {
map.put(1, "ValueA");
});
Thread b = new Thread(() -> {
map.put(1, "ValueB");
});
a.start();
b.start();
如果线程 A 首先执行,它将键 1 与 “ValueA” 关联。然后,线程 B 执行并尝试将相同的键 1 与 “ValueB” 关联。这可能导致 “ValueA” 被 “ValueB” 覆盖,而没有适当的同步机制来处理这种冲突。
2. 数据竞争
考虑两个线程同时尝试读取和写入 HashMap 中的相同元素。
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "Initial");
Thread reader = new Thread(() -> {
System.out.println("Value read: " + map.get(1));
});
Thread writer = new Thread(() -> {
map.put(1, "Updated");
});
reader.start();
writer.start();
如果 writer
线程首先执行并更新了键 1 的值,然后 reader
线程尝试读取该值,它可能会得到 “Updated” 或 “Initial”,这取决于线程的执行顺序和 JVM 的内存模型。
3. 扩容时的问题
当 HashMap 需要扩容时,它会创建一个新的内部数组并重新分配现有的键值对。如果多个线程触发了这个操作,可能会发生数据丢失。
HashMap<Integer, String> largeMap = new HashMap<>();
// 假设这个循环由多个线程共享
for (int i = 0; i < 1000000; i++) {
largeMap.put(i, "Value" + i);
}
如果多个线程尝试填充这个 HashMap 并触发扩容,可能会导致键值对的丢失或不正确的映射。
4. 迭代器失效
当迭代 HashMap 时,如果其他线程修改了 HashMap,迭代器可能会抛出 ConcurrentModificationException
。
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "One");
Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, String> entry = it.next();
// 假设另一个线程正在修改 map
map.remove(1); // 这可能会在迭代过程中抛出 ConcurrentModificationException
}
解决方法:
使用 Collections.synchronizedMap:
Map<Integer, String> safeMap = Collections.synchronizedMap(new HashMap<>());
使用 ConcurrentHashMap:
Map<Integer, String> concurrentMap = new ConcurrentHashMap<>();
手动同步:
synchronized (map) {
map.put(key, value);
}