面试题:为什么说HashMap是线程不安全的?有什么解决方法?

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);
}
  • 25
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值