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

简介: ConcurrentHashMap是Java中线程安全的哈希表实现,支持高并发读写操作。相比Hashtable,它通过分段锁(JDK1.7)或CAS+synchronized(JDK1.8)实现更细粒度锁控制,提升性能与安全性。本文详细介绍其构造方法、添加/获取/删除元素等常用操作,并对比JDK1.7和1.8的区别,帮助开发者深入理解与使用ConcurrentHashMap。欢迎关注,了解更多!

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

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

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

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

什么是ConcurrentHashMap?

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

下图为JDK1.7和1.8的ConcurrentHashMap:

image.png

JDK1.8:

image.png

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

对比维度 JDK 1.7 JDK 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。

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

目录
相关文章
|
1月前
|
存储 安全 Java
【Java并发】【原子类】适合初学体质的原子类入门
什么是CAS? 说到原子类,首先就要说到CAS: CAS(Compare and Swap) 是一种无锁的原子操作,用于实现多线程环境下的安全数据更新。 CAS(Compare and Swap) 的
65 15
【Java并发】【原子类】适合初学体质的原子类入门
|
1月前
|
Java
【源码】【Java并发】【ReentrantLock】适合中学者体质的ReentrantLock源码阅读
因为本文说的是ReentrantLock源码,因此会默认,大家对AQS有基本的了解(比如同步队列、条件队列大概> 长啥样?)。 不懂AQS的小朋友们,你们好呀!也欢迎先看看这篇
81 13
【源码】【Java并发】【ReentrantLock】适合中学者体质的ReentrantLock源码阅读
|
1月前
|
Java
【源码】【Java并发】【ConcurrentHashMap】适合中学体质的ConcurrentHashMap
本文深入解析了ConcurrentHashMap的实现原理,涵盖JDK 7与JDK 8的区别、静态代码块、构造方法、put/get/remove核心方法等。JDK 8通过Node数组+链表/红黑树结构优化并发性能,采用CAS和synchronized实现高效锁机制。文章还详细讲解了hash计算、表初始化、扩容协助及计数更新等关键环节,帮助读者全面掌握ConcurrentHashMap的工作机制。
72 6
【源码】【Java并发】【ConcurrentHashMap】适合中学体质的ConcurrentHashMap
|
1月前
|
Java
【源码】【Java并发】【LinkedBlockingQueue】适合中学体质的LinkedBlockingQueue入门
前言 有了前文对简单实用的学习 【Java并发】【LinkedBlockingQueue】适合初学体质的LinkedBlockingQueue入门 聪明的你,一定会想知道更多。哈哈哈哈哈,下面主播就...
51 6
【源码】【Java并发】【LinkedBlockingQueue】适合中学体质的LinkedBlockingQueue入门
|
1月前
|
安全 Java
【Java并发】【ArrayBlockingQueue】适合初学体质的ArrayBlockingQueue入门
什么是ArrayBlockingQueue ArrayBlockingQueue是 Java 并发编程中一个基于数组实现的有界阻塞队列,属于 java.util.concurrent 包,实现了 Bl...
58 6
【Java并发】【ArrayBlockingQueue】适合初学体质的ArrayBlockingQueue入门
|
1月前
|
安全 Java
【源码】【Java并发】【ArrayBlockingQueue】适合中学者体质的ArrayBlockingQueue
前言 通过之前的学习是不是学的不过瘾,没关系,马上和主播来挑战源码的阅读 【Java并发】【ArrayBlockingQueue】适合初学体质的ArrayBlockingQueue入门 还有一件事
52 5
【源码】【Java并发】【ArrayBlockingQueue】适合中学者体质的ArrayBlockingQueue
|
1月前
|
监控 Java API
【Java并发】【ReentrantLock】适合初学体质的ReentrantLock入门
前言 什么是ReentrantLock? ReentrantLock 是 Java 并发包 (java.util.concurrent.locks) 中的一个类,它实现了 Lock 接口,提供了与
82 10
【Java并发】【ReentrantLock】适合初学体质的ReentrantLock入门
|
1月前
|
安全 Java
【Java并发】【LinkedBlockingQueue】适合初学体质的LinkedBlockingQueue入门
前言 你是否在线程池工具类里看到过它的身影? 你是否会好奇LinkedBlockingQueue是啥呢? 没有关系,小手手点上关注,跟上主播的节奏。 什么是LinkedBlockingQueue? ...
51 1
【Java并发】【LinkedBlockingQueue】适合初学体质的LinkedBlockingQueue入门
|
存储 Java
Java集合源码解析-ConcurrentHashMap(JDK8)(下)
Java集合源码解析-ConcurrentHashMap(JDK8)
163 0
Java集合源码解析-ConcurrentHashMap(JDK8)(下)
|
Java 调度
Java集合源码解析-ConcurrentHashMap(JDK8)(中)
Java集合源码解析-ConcurrentHashMap(JDK8)
177 0
Java集合源码解析-ConcurrentHashMap(JDK8)(中)

热门文章

最新文章