【Java并发】【ConcurrentHashMap】适合初学体质的ConcurrentHashMap入门

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD

🔥 2025本人正在沉淀中… 博客更新速度++

👍 欢迎点赞、收藏、关注,跟上我的更新节奏

📚欢迎订阅专栏,专栏名《在2B工作中寻求并发是否搞错了什么》

什么是ConcurrentHashMap?

ConcurrentHashMap 是 Java 中线程安全的哈希表实现,支持高并发读写操作。与 Hashtable 相比,它通过分段锁(JDK1.7)或 CAS + synchronized(JDK1.8)实现更细粒度的锁控制,兼顾性能与线程安全。

下图为JDK1.7和1.8的ConcurrentHashMap:

在这里插入图片描述

JDK1.8:

在这里插入图片描述

让我们也对比下JDK1.7和JDK1.8的区别吧!

对比维度JDK 1.7JDK 1.8
数据结构Segment 数组(分段锁) + HashEntry 数组 + 链表Node 数组 + 链表/红黑树(无 Segment)
锁机制使用 ReentrantLock 锁住整个 Segment使用 synchronized 锁单个桶(链表头节点或红黑树根节点),结合 CAS 无锁操作
并发粒度锁粒度较粗(Segment 级别,默认 16 个 Segment)锁粒度更细(桶级别,并发度由数组长度动态决定)
哈希冲突处理仅链表结构链表长度 ≥8 时转为红黑树(优化查询效率)
扩容机制每个 Segment 独立扩容(头插法)多线程协同扩容(尾插法),避免链表死循环
size() 实现遍历所有 Segment 统计(需多次加锁,不精确)基于 CounterCell 的分段计数(无锁,高效且精确)
查询效率O(n) 链表遍历O(1) 链表或 O(logN) 红黑树
内存占用较高(Segment 结构额外开销)更低(简化结构,去除了 Segment 层)
线程安全设计分段锁隔离写操作CAS + synchronized 精细化控制
典型适用场景中等并发场景高并发、大规模数据场景

简单使用ConcurrentHashMap

构造方法

那让我们先从构造方法说起:

在这里插入图片描述

// 默认构建:默认初始容量 16,负载因子 0.75
ConcurrentHashMap<>();

// 指定初始容量:根据初始容量自动计算扩容阈值。
ConcurrentHashMap(int initialCapacity) 

// 完整参数构造:concurrencyLevel 仅为了兼容旧版本,JDK 1.8+ 中实际并发控制通过 CAS + synchronized 实现。
ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
    this(initialCapacity, loadFactor, 1);
}

// 将给定 Map 的键值对复制到新实例中。
ConcurrentHashMap(Map<? extends K, ? extends V> m) 

简单的例子

让我们从一个简答的例子来了解ConcurrentHashMap的使用吧!之后再具体说说各个方法:

public static void main(String[] args) {
    // 1. 初始化
    ConcurrentHashMap<String, Integer> scores = new ConcurrentHashMap<>();

    // 2. 插入数据(线程安全)
    scores.put("Alice", 90);
    scores.put("Bob", 85);
    
    // 3. 原子操作:若不存在则插入
    scores.putIfAbsent("Charlie", 100); // 只有"Charlie"不存在时才会插入
    
    // 4. 原子更新:线程安全的累加(经典计数场景)
    scores.compute("Alice", (key, oldValue) -> {
        if (oldValue == null) return 1;
        return oldValue + 10; // 将 Alice 的分数加10
    });

    // 5. 线程安全读取
    System.out.println("Bob's score: " + scores.get("Bob"));

    // 6. 遍历(弱一致性迭代器)
    scores.forEach((name, score) -> 
        System.out.println(name + ": " + score)
    );

    // 7. 合并操作(原子性合并值)
    scores.merge("Bob", 5, Integer::sum); // 如果 Bob 存在,分数+5
}

添加元素

put(K key, V value)

  • 功能:插入键值对(若键已存在则覆盖旧值)。
  • 线程安全机制:JDK8+ 使用 synchronized 锁定哈希桶的头节点(桶级别锁)。
  • 适用场景:简单的键值插入或覆盖。
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("apple", 10);  // 插入 apple=10
map.put("apple", 20);  // 覆盖为 apple=20

putIfAbsent(K key, V value)

  • 功能:仅当键不存在时才插入值(原子操作)。
  • 线程安全机制:基于 CAS 或 synchronized 保证原子性。
  • 适用场景:避免重复初始化(如缓存懒加载)。`
// 多线程安全地初始化
map.putIfAbsent("banana", 5);  // 若 banana 不存在,则插入 5
map.putIfAbsent("banana", 10); // 此处不会覆盖,值仍为 5

compute(K key, BiFunction remappingFunction)

  • 功能:原子性地根据旧值计算新值(允许删除或更新)。
  • 线程安全机制:锁定当前桶,保证计算过程的原子性。
  • 适用场景:复杂更新逻辑(如累加、条件删除)。
// 原子累加:若存在则 +5,若不存在则设为 0
map.compute("apple", (key, oldVal) -> {
    return (oldVal == null) ? 0 : oldVal + 5;
});

computeIfAbsent(K key, Function mappingFunction)

  • 功能:若键不存在,则通过函数生成值并插入。
  • 线程安全机制:仅在键不存在时触发计算,避免重复初始化。
  • 适用场景:懒加载昂贵资源(如缓存、数据库连接)。
// 多线程下只初始化一次
map.computeIfAbsent("orange", k -> {
    // 模拟耗时操作(如从数据库加载)
    return fetchPriceFromDB(k); 
});

merge(K key, V value, BiFunction remapFunction)

  • 功能:合并新值和旧值(若键存在则合并,否则直接插入)。
  • 线程安全机制:原子操作,类似 compute 但更简洁。
  • 适用场景:合并统计(如计数、求和)。
// 线程安全的计数
map.merge("apple", 1, (oldVal, newVal) -> oldVal + newVal); // 若存在则 +1,否则设为 1

replace(K key, V oldValue, V newValue)

  • 功能:仅当键的当前值等于 oldValue 时,才替换为 newValue
  • 线程安全机制:基于 CAS 或锁的原子操作。
  • 适用场景:条件更新(类似乐观锁)。
// 只有当前值是 20 时才更新为 30
map.replace("apple", 20, 30); 

获取元素

get(Object key)

  • 功能:根据键获取对应的值。

  • 线程安全机制:完全无锁,依赖 volatile 关键字保证可见性。

  • 特点

    • 如果键不存在,返回 null
    • 由于弱一致性,可能无法立即反映其他线程的最新修改(但最终会一致)。
  • 适用场景:快速读取键对应的值,无需同步。

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("apple", 10);
Integer value = map.get("apple"); // 返回 10
Integer missing = map.get("banana"); // 返回 null

getOrDefault(Object key, V defaultValue)

  • 功能:获取键对应的值,若键不存在则返回默认值。
  • 线程安全机制:与 get 相同(无锁)。
  • 优点:避免因键不存在而直接返回 null 导致的空指针问题。
  • 适用场景:需要默认值的读取操作。
int count = map.getOrDefault("banana", 0); // 若不存在,返回 0

containsKey(Object key)

  • 功能:检查键是否存在。
  • 线程安全机制:无锁,通过哈希表结构快速定位。
  • 注意
    • get 类似,结果可能受并发修改影响(弱一致性)。
    • 若需要原子性检查是否存在并操作,应使用 putIfAbsentcompute 等方法。
if (map.containsKey("apple")) {
    // 注意:此处其他线程可能已经删除了 "apple"
    System.out.println("Apple exists!");
}

containsValue(Object value)

  • 功能:检查值是否存在(遍历整个哈希表)。

  • 线程安全机制:无锁,但需要遍历所有桶。

  • 缺点

    • 性能较差(时间复杂度 O(n))。
    • 结果可能不一致(遍历过程中其他线程可能修改数据)。
  • 适用场景:低频的全局值检查。

boolean hasValue = map.containsValue(10); // 检查是否存在值为 10 的条目

forEach(BiConsumer<? super K, ? super V> action)

  • 功能:遍历所有键值对。
  • 线程安全机制:弱一致性迭代器,遍历时可能跳过或包含并发修改的条目。
  • 特点:不会抛出 ConcurrentModificationException
map.forEach((key, value) -> 
    System.out.println(key + ": " + value)
);

search(Function<Map.Entry<K, V>, ? extends U> searchFunction)

  • 功能:并行搜索符合条件的条目,返回第一个匹配结果。
  • 线程安全机制:遍历时无锁,但结果可能受并发修改影响。
  • 适用场景:高效并行查找。
// 查找第一个值为 20 的键
String result = map.search(1, (k, v) -> v == 20 ? k : null);

mappingCount()

  • 功能:返回映射的总条目数(long 类型)。
  • 线程安全机制:基于无锁统计,近似值但比 size() 更准确。
  • 注意:优先使用 mappingCount() 替代 size() ,避免 size() 返回 int 溢出问题。
long count = map.mappingCount(); // 总条目数(近似值)

删除元素

remove(Object key)

  • 功能:根据键删除对应的键值对。
  • 线程安全机制:锁定哈希桶的头节点(synchronized),保证原子性。
  • 返回值:被删除的值(若键存在),否则返回 null
  • 特点:简单删除,不关心旧值。
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("apple", 10);
Integer removedValue = map.remove("apple"); // 返回 10
Integer noKey = map.remove("banana");       // 返回 null

remove(Object key, Object value)

  • 功能:仅当键的当前值与指定值匹配时,才删除键值对(原子操作)。
  • 线程安全机制:基于 CAS 或锁的原子性检查。
  • 返回值:删除成功返回 true,否则返回 false
  • 适用场景:乐观锁式删除(类似“检查再删除”的原子操作)。
map.put("apple", 10);
boolean isRemoved = map.remove("apple", 10); // true(键存在且值匹配)
boolean notRemoved = map.remove("apple", 5); // false(值不匹配)

computeIfPresent(K key, BiFunction remappingFunction)

  • 功能:若键存在,则根据旧值计算新值(新值可为 null,表示删除键)。
  • 线程安全机制:锁定当前桶,保证计算和更新的原子性。
  • 适用场景:条件删除或更新(例如:根据旧值逻辑删除)。
map.put("apple", 10);

// 若 apple 存在且值 >=10,则删除(返回 null)
map.computeIfPresent("apple", (k, v) -> v >= 10 ? null : v);

// 结果:apple 被删除

replace(K key, V oldValue, V newValue)

  • 功能:仅当键的当前值等于 oldValue 时,替换为 newValue
  • 线程安全机制:原子操作(CAS 或锁)。
  • 返回值:替换成功返回 true,否则 false
  • 扩展用法:若 newValue 设为 null,可实现条件删除。
map.put("apple", 10);
boolean replaced = map.replace("apple", 10, 20); // true(替换为 20)
boolean deleted = map.replace("apple", 20, null); // true(替换为 null,等同于删除)

clear()

  • 功能:清空所有键值对。
  • 线程安全机制:逐步清空每个哈希桶(非原子性,但线程安全)。
  • 注意:清空操作期间,其他线程仍可并发读写未处理的桶。
map.clear(); // 清空所有条目

后话

怎么样,聪明的你对ConcurrentHashMap有所了解呢?

没有关系,下一篇才是重中之中!主播将会邀请与您一起共同阅读ConcurrentHashMap。

小手手点上关注,跟上主播的节奏!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值