(JUC)HashMap 如何保证线程安全?

在 Java 开发中,HashMap 是最常用的键值对存储容器之一。但它本身不是线程安全的,在多线程环境下容易出现数据丢失、死循环(JDK7 扩容链表环)、读到旧数据等问题。因此,在并发场景下需要额外手段保证安全。

本文将从 Collections.synchronizedMap ConcurrentHashMap 两条主线展开,结合一些零散的知识点,系统梳理 HashMap 的线程安全解决方案。


1. HashMap 为什么线程不安全?

  • 并发写入数据丢失:两个线程同时 put,可能覆盖数据。

  • 扩容时死循环:JDK7 链表扩容时头插法可能导致环形链表,get 操作死循环。

  • 读写不一致:线程 A 写入的数据,线程 B 可能不可见。

因此,在并发场景下不能直接使用 HashMap


2. Collections.synchronizedMap

使用方式

Map<K, V> map = Collections.synchronizedMap(new HashMap<>());

原理

  • 给所有方法加上 synchronized,保证同一时刻只有一个线程能访问。

  • 等效于给整个 HashMap 加一把大锁。

优缺点

  • ✅ 简单易用,适合低并发场景。

  • ❌ 并发性能差,所有操作都要竞争同一把锁。


3. ConcurrentHashMap

这是并发场景下最推荐的方案。JDK7 和 JDK8 实现有较大差异。

3.1 JDK 7 实现 —— 分段锁 (Segment + ReentrantLock)

  • Segment:相当于一个小 HashMap,内部维护一个数组和链表结构。

  • 分段锁:每个 Segment 一把 ReentrantLock,不同 Segment 之间可以并发操作。

  • 默认 16 个 Segment,也就是并发度为 16。

优点:比全表锁更高效。

缺点:Segment 数量固定,扩展性不足。

3.2 JDK 8 实现 —— CAS + synchronized + 桶级锁

  • 取消 Segment:只保留一个 Node[] table

  • CAS 插入:创建新桶时用 CAS 保证线程安全。

  • synchronized 桶级锁:只对冲突的桶 (table[i]) 加锁,粒度比 Segment 更小。

  • 链表 → 红黑树:链表过长时转为红黑树,提高查找效率。

优点:内存利用率更高,锁粒度更细,并发性能更好。


4. 锁粒度的意义

  • 大粒度锁:锁住整个 Map,保证安全,但并发度低(例如 synchronizedMap、Hashtable)。

  • 中等粒度锁:锁住部分区域(Segment),性能较好(JDK7 ConcurrentHashMap)。

  • 小粒度锁:锁住单个桶(table[i]),最大化并发度(JDK8 ConcurrentHashMap)。

👉 核心思想:锁粒度越小,并发性能越高。


5. ReentrantLock vs synchronized

特性ReentrantLocksynchronized
出现时间JDK 1.5 并发包JDK 1.0
公平锁支持公平/非公平只能非公平
可中断锁支持不支持
可超时锁支持不支持
性能JDK7 前更优JDK8 后优化后差距缩小
使用方式手动加解锁自动加解锁

6. 其他保证线程安全的方式

  1. Hashtable

    • JDK1.0 的线程安全 Map,所有方法都加了 synchronized。

    • 性能差,几乎被 ConcurrentHashMap 替代。

  2. 手动加锁封装

    private final Map<K, V> map = new HashMap<>();
    
    public synchronized void put(K key, V value) {
        map.put(key, value);
    }
    
    public synchronized V get(K key) {
        return map.get(key);
    }
    
  3. ReadWriteLock 封装

    • 读操作共享,写操作独占。

    • 适合读多写少的场景。


7. 总结

  • HashMap:线程不安全。

  • Collections.synchronizedMap:简单加锁,低并发场景可用。

  • Hashtable:古老方案,几乎淘汰。

  • ConcurrentHashMap:并发场景首选。

    • JDK7:Segment + ReentrantLock,分段锁。

    • JDK8:CAS + synchronized,锁粒度缩小到桶级别,性能更优。

一句话总结:在并发编程中,不要直接用 HashMap,而是根据场景选择 synchronizedMapConcurrentHashMap,后者是主流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值