【Java第87集】java ConcurrentHashMap类详解

ConcurrentHashMap 是 Java 并发包 (java.util.concurrent) 中的一个线程安全的哈希表实现,专为高并发场景设计。它通过 分段锁(Java 7)或 CAS + synchronized(Java 8 及以后)机制,显著提升了并发性能,同时支持动态扩容、链表转红黑树等优化。以下从多个维度详细介绍其核心特性、底层实现及使用方法。

一、ConcurrentHashMap的核心特性

1. 线程安全

  • 所有操作(如 putgetremove)在多线程环境下保证线程安全。
  • HashtablesynchronizedMap 的区别
    • HashtablesynchronizedMap 对整个 Map 加锁,导致并发性能差。
    • ConcurrentHashMap 使用 分段锁(Java 8 之前)或 CAS + synchronized(Java 8 及以后),锁粒度更细,允许多个线程同时操作不同部分。

2. 高并发性能

  • Java 8 之前:使用分段锁(Segment),每个段独立加锁,减少锁竞争。
  • Java 8 及以后:采用 CAS 操作 + synchronized 锁,进一步降低锁开销,提升吞吐量。
  • 弱一致性迭代器:迭代器不会抛出 ConcurrentModificationException,但可能反映部分更新。

3. 支持原子操作

  • 提供 putIfAbsentremovereplace 等原子方法,避免手动加锁。
  • Java 8+ 新增 computeIfAbsentforEach 等方法,简化并发编程。

4. 动态扩容

  • 支持自动扩容,当元素数量超过阈值时重新分配数组,无需阻塞所有线程。

二、ConcurrentHashMap的底层实现

1. Java 7 的实现(分段锁)

数据结构

  • Segment 数组:Segment[] segments,每个 Segment 是一个独立的锁和小型哈希表。每个 Segment 内部维护一个 HashEntry[] 数组,每个 HashEntry 节点存储键值对,并通过链表解决哈希冲突。
  • 锁粒度:每个段独立加锁,修改操作仅锁定相关段,其他段可并发访问。
  • 缺点:分段锁在并发度极高时仍存在锁竞争问题。

核心机制

  • Segment 分段:默认 16 个段,每个段对应一部分桶(Bucket)。
  • HashEntry 不可变HashEntrykeyhashnextfinalvaluevolatile,保证读操作无需加锁。

代码示例(简化版)

public class ConcurrentHashMap<K, V> {
    // Segment 数组,分段锁的核心结构
    final Segment<K, V>[] segments;

    // Segment 内部类(Java 7)
    static final class Segment<K, V> extends ReentrantLock {
        // 哈希桶数组
        volatile HashEntry<K, V>[] table;
        // 元素数量
        transient int count;
        // 修改次数
        transient int modCount;
        // 扩容阈值
        transient int threshold;
        // 负载因子
        final float loadFactor;

        // HashEntry 节点
        static final class HashEntry<K, V> {
            final int hash;
            final K key;
            volatile V value;
            volatile HashEntry<K, V> next;

            HashEntry(int hash, K key, V value, HashEntry<K, V> next) {
                this.hash = hash;
                this.key = key;
                this.value = value;
                this.next = next;
            }
        }
    }
}

2. Java 8 及以后的实现(CAS + synchronized)

数据结构

  • 数组 + 链表/红黑树:底层结构与 HashMap 类似,但通过 CAS 操作和 synchronized 锁控制并发。
  • 锁粒度:对链表的头节点或红黑树的根节点加 synchronized 锁,锁粒度更细。

核心优化

  • CAS 操作:用于无锁化初始化/扩容和插入空桶。
  • 链表转红黑树:当链表长度超过阈值(默认 8)且数组长度大于 64 时,链表转换为红黑树。
  • 并发扩容:使用 ForwardingNode 逐步迁移数据,避免全局锁。

示例代码

// Node 节点(Java 8)
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;
    Node(int hash, K key, V val, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.val = val;
        this.next = next;
    }
}

// 红黑树节点(Java 8)
static final class TreeNode<K,V> extends Node<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
}

三、ConcurrentHashMap的核心方法

方法描述
V put(K key, V value)插入键值对,若键已存在则替换。
V get(Object key)获取指定键的值。
V remove(Object key)移除指定键的值。
boolean containsKey(Object key)判断是否包含指定键。
void clear()清空所有键值对。
V putIfAbsent(K key, V value)如果键不存在,则插入值。
boolean remove(Object key, Object value)如果键对应的值等于指定值,则移除。
boolean replace(K key, V oldValue, V newValue)如果键的旧值等于指定值,则替换为新值。
V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)如果键不存在或值为 null,则计算并插入新值。
void forEach(BiConsumer<? super K, ? extends V> action)遍历所有键值对,执行操作。

示例代码

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("Apple", 10); // 插入键值对
map.putIfAbsent("Banana", 5); // 如果键不存在则插入
map.computeIfAbsent("Cherry", k -> 15); // 计算并插入新值
System.out.println(map.get("Apple")); // 输出 10

四、ConcurrentHashMap与Hashtable/Collections.synchronizedMap的对比

特性ConcurrentHashMapHashtableCollections.synchronizedMap
线程安全✅ 是✅ 是✅ 是
锁粒度分段锁(Java 8 前) / CAS + synchronized(Java 8+)全局锁全局锁
并发性能
迭代器一致性弱一致性快速失败快速失败
原子操作✅ 支持(如 putIfAbsent❌ 不支持❌ 不支持
适用场景高并发读写低并发场景低并发场景

五、ConcurrentHashMap的使用场景

1. 高频读写的缓存

  • 适用于缓存系统(如 Redis 客户端本地缓存),需要高效并发访问和原子更新。

2. 分布式计数器

  • 多线程环境下统计计数(如页面访问量),避免手动加锁。

3. 任务分组

  • 在生产者-消费者模型中,按任务类型分组存储和处理任务。

4. 配置管理

  • 动态加载和更新配置项,支持并发读取和局部更新。

六、ConcurrentHashMap的注意事项

1. 弱一致性迭代器

  • 迭代器不会抛出 ConcurrentModificationException,但可能反映部分更新(如新增的元素可能未被遍历到)。

2. 避免死锁

  • 使用 computeIfAbsent 时,注意避免递归调用导致死锁(例如在映射函数中再次调用 put)。

3. 内存一致性

  • 写操作对其他线程的可见性通过 volatile 和 CAS 保证,符合 Java 内存模型规范。

七、ConcurrentHashMap的参考代码

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, AtomicInteger> counterMap = new ConcurrentHashMap<>();

        // 多线程并发操作
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                String key = "item" + (i % 10);
                counterMap.computeIfAbsent(key, k -> new AtomicInteger(0)).incrementAndGet();
            }
        };

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出结果
        counterMap.forEach((key, value) -> {
            System.out.println(key + ": " + value);
        });
    }
}

八、总结

ConcurrentHashMap 是 Java 中高性能线程安全 Map 的首选实现,尤其适合高并发读写场景。其核心优势在于通过分段锁(Java 8 前)或 CAS + synchronized(Java 8+)机制,显著降低了锁竞争,提升了吞吐量。与 HashtablesynchronizedMap 相比,它在性能和功能上更具优势。理解其内部实现和适用场景,有助于在并发编程中高效使用这一工具。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值
OSZAR »