讲师中心 微信公众号
AI工具推荐 视频效率加速

Java中ConcurrentHashMap的使用方法

风萱姑娘_9928

风萱姑娘_9928

发布时间:2025-09-19 13:05:01

|

701人浏览过

|

来源于php中文网

原创

ConcurrentHashMap是Java中线程安全且高性能的哈希表实现,适用于多线程环境下高效操作键值对。它通过CAS操作和synchronized锁节点实现高并发读写,避免了HashTable的全局锁性能瓶颈。与HashMap相比,它支持并发修改而不抛出异常;与HashTable相比,其分段锁或节点级锁机制显著提升并发性能。在Java 8中,底层采用Node数组+链表/红黑树结构,put操作先CAS插入再必要时加锁,get操作无锁但保证可见性。推荐在多线程共享数据场景使用,如缓存、计数器等。注意其不允许null键或值,迭代器为弱一致性,复合操作应使用compute、merge等原子方法以避免竞态条件。合理设置初始容量可减少扩容开销,同时需关注键的hashCode均匀性及内存占用问题。

java中concurrenthashmap的使用方法

ConcurrentHashMap是Java并发编程中不可或缺的利器,它提供了一种线程安全且高性能的哈希表实现。简单来说,当你需要在多线程环境下安全、高效地操作一个键值对集合时,ConcurrentHashMap往往是你的首选,因为它在保证数据一致性的同时,最大程度地提升了并发性能,避免了传统HashTable或Collections.synchronizedMap()带来的性能瓶颈。

解决方案

使用ConcurrentHashMap非常直接,它提供了与HashMap类似的API,但在内部处理了所有的并发细节。

首先,你需要创建一个ConcurrentHashMap实例。通常,我们不需要指定初始容量,但在处理大量数据时,预估一个合理的初始容量(或使用

new ConcurrentHashMap<>(initialCapacity)
)可以减少扩容的开销。

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ConcurrentHashMapDemo {

    public static void main(String[] args) throws InterruptedException {
        ConcurrentHashMap<String, Integer> userScores = new ConcurrentHashMap<>();

        // 基本的put操作
        userScores.put("Alice", 100);
        userScores.put("Bob", 95);
        System.out.println("初始分数: " + userScores); // 输出: 初始分数: {Alice=100, Bob=95}

        // 基本的get操作
        Integer aliceScore = userScores.get("Alice");
        System.out.println("Alice的分数: " + aliceScore); // 输出: Alice的分数: 100

        // 基本的remove操作
        userScores.remove("Bob");
        System.out.println("移除Bob后: " + userScores); // 输出: 移除Bob后: {Alice=100}

        // putIfAbsent: 如果key不存在,则放入;如果存在,则不操作并返回旧值
        userScores.putIfAbsent("Alice", 110); // Alice已存在,不会更新
        userScores.putIfAbsent("Charlie", 88); // Charlie不存在,会放入
        System.out.println("使用putIfAbsent后: " + userScores); // 输出: 使用putIfAbsent后: {Alice=100, Charlie=88}

        // compute: 原子地计算并更新一个值
        // 假设我们要给Alice的分数加10
        userScores.compute("Alice", (key, oldValue) -> oldValue == null ? 0 : oldValue + 10);
        System.out.println("Alice分数更新后: " + userScores.get("Alice")); // 输出: Alice分数更新后: 110

        // merge: 如果key存在,则使用remappingFunction合并旧值和新值;如果key不存在,则放入新值
        userScores.merge("Alice", 5, (oldValue, newValue) -> oldValue + newValue); // 110 + 5 = 115
        userScores.merge("David", 70, (oldValue, newValue) -> oldValue + newValue); // David不存在,直接放入70
        System.out.println("使用merge后: " + userScores); // 输出: 使用merge后: {Alice=115, Charlie=88, David=70}

        // 遍历ConcurrentHashMap
        // 注意:迭代器是弱一致性的,它反映的是在迭代器创建时或创建后某个时刻的映射状态,不保证实时性。
        // 但它不会抛出ConcurrentModificationException。
        System.out.println("遍历ConcurrentHashMap:");
        userScores.forEach((user, score) -> System.out.println(user + ": " + score));

        // 模拟多线程并发操作
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 100; i++) {
            final String user = "User" + (i % 10); // 10个用户
            executor.submit(() -> {
                userScores.compute(user, (k, v) -> v == null ? 1 : v + 1);
            });
        }
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        System.out.println("并发更新后: " + userScores);
        // 理论上每个用户的值都应该是10,因为有100次操作,10个用户,每个用户被操作了10次。
        // 验证:userScores.get("User0") 应该等于10
    }
}

在实际应用中,

putIfAbsent
、
compute
和
merge
这些方法尤其重要,它们提供了原子性的复合操作,避免了“先检查后执行”可能导致的并发问题。例如,如果你想给一个计数器加一,直接使用
compute
比
get
再
put
要安全得多。

立即学习“Java免费学习笔记(深入)”;

ConcurrentHashMap与HashMap、HashTable有何本质区别,何时选择ConcurrentHashMap?

这个问题,我个人觉得是理解ConcurrentHashMap价值的关键。我们先从源头说起:

HashMap
:这是我们日常开发中最常用的哈希表,效率极高。但它天生就是为单线程环境设计的,在多线程下,如果你不加任何同步措施就去操作它,那简直是一场灾难。数据丢失、无限循环、内存溢出……各种诡异的Bug会层出不穷。所以,
HashMap
是“快但不安全”的代表。

HashTable
:Java早期的线程安全哈希表实现。它通过在所有公共方法上加
synchronized
关键字来保证线程安全。听起来不错,但这意味着任何时候,只有一个线程能访问
HashTable
的任何一个方法。当一个线程在
put
数据时,其他线程无论是想
get
还是
put
,都得老老实实地等着。这种“全局锁”的机制,在并发量高的时候,性能会急剧下降,几乎完全串行化了。所以,
HashTable
是“安全但慢”的典型。

ConcurrentHashMap
:它就是为了解决
HashTable
的性能瓶颈而生的。它的核心思想是“分段锁”或者更精确地说是“节点锁”。在Java 7及以前,它通过
Segment
(分段)来实现,每个
Segment
自身是一个独立的
ReentrantLock
,只锁定哈希表的一部分,这样不同的线程就可以同时操作不同的
Segment
,大大提升了并发度。到了Java 8,实现方式有所变化,它抛弃了
Segment
,转而采用
CAS
(Compare-And-Swap)操作和对
Node
(节点)进行
synchronized
锁定的方式。当哈希冲突严重时,链表会转换为红黑树,进一步优化性能。这种设计让
ConcurrentHashMap
在保证线程安全的同时,能提供接近
HashMap
的性能。

何时选择ConcurrentHashMap?

我的经验是,只要你的应用是多线程的,并且你需要一个共享的、可变的数据结构来存储键值对,那么几乎总是应该考虑

ConcurrentHashMap
。

java-api-doc-generator
java-api-doc-generator

分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。

下载
  • 高并发读写场景: 例如,一个缓存系统,多个线程需要同时查询和更新缓存项。
  • 统计计数: 多个线程需要对某个事件进行计数,
    compute
    或
    merge
    方法可以非常优雅地实现原子计数。
  • 共享配置或状态: 当多个组件或服务需要访问和修改同一个配置或状态集合时。

如果你确定是单线程环境,或者只是作为局部变量使用,那

HashMap
无疑是更轻量、更快的选择。而
HashTable
,说实话,现在已经很少有场景会主动去使用了,
ConcurrentHashMap
在几乎所有方面都优于它。

ConcurrentHashMap的底层实现原理是怎样的?(Java 8及以后版本)

理解ConcurrentHashMap的内部机制,能让我们更好地驾驭它,甚至在遇到一些“奇特”行为时能有所预判。Java 8的ConcurrentHashMap实现与Java 7及之前版本有显著不同,放弃了

Segment
分段锁的模式,转而采用了一种更细粒度的锁定策略:CAS操作结合
synchronized
锁
。

简单来说,它的底层是一个

Node<K,V>[] table
数组,每个数组元素可能是一个链表头,也可能是红黑树的根节点。

  1. 初始化与扩容:

    • table
      数组的初始化是懒惰的,只有第一次
      put
      操作时才会进行。
    • 扩容(
      resize
      )发生在
      table
      容量不足时。与
      HashMap
      类似,它会创建一个两倍大小的新数组,并将旧数组的元素迁移过去。但这个迁移过程是并发友好的,通过
      ForwardingNode
      和辅助线程来协同完成,避免了长时间的全局停顿。
  2. put
    操作的核心流程:

    • 计算哈希: 首先,对键进行哈希处理,定位到
      table
      数组中的索引位置。
    • CAS尝试: 如果该位置为空,
      ConcurrentHashMap
      会尝试使用
      CAS
      操作(
      Unsafe.compareAndSwapObject
      )直接将新的
      Node
      放置进去。这是非阻塞的,效率很高。
    • 加锁处理: 如果该位置不为空(说明已经有元素或正在进行扩容),那么
      ConcurrentHashMap
      会锁定该索引位置的头节点(或
      ForwardingNode
      )。注意,这里使用的是
      synchronized
      关键字,锁的是具体的
      Node
      对象,而不是整个
      table
      。
    • 链表/红黑树操作: 在获得锁之后,线程会检查该位置的结构。
      • 如果是链表,就遍历链表,如果找到相同的键,就更新值;如果没找到,就添加到链表尾部。
      • 如果链表长度超过阈值(默认8),链表会转换为红黑树,以保证在极端哈希冲突下的查找效率为O(logN)。
      • 如果是红黑树,则按照红黑树的规则进行插入或更新。
    • 计数与扩容:
      put
      成功后,会原子地更新
      size
      计数器。如果
      size
      超过了阈值,就会触发扩容。
  3. get
    操作:

    • get
      操作是完全无锁的。它也是先计算哈希,然后定位到
      table
      数组的索引位置。
    • 接着,遍历链表或红黑树找到对应的键。由于
      Node
      的
      value
      字段是
      volatile
      的,所以
      get
      操作能够保证读取到最新的值。
    • 这种无锁读取的机制,是
      ConcurrentHashMap
      高并发读性能的关键。
  4. volatile
    与
    CAS
    :
    ConcurrentHashMap
    大量使用了
    volatile
    关键字来保证内存可见性,以及
    CAS
    操作来保证一些关键操作的原子性,例如在数组槽位上放置第一个节点。当
    CAS
    失败时,才会退化到
    synchronized
    锁。

总结一下,Java 8的ConcurrentHashMap通过

CAS
的乐观锁尝试和
synchronized
的悲观锁(针对单个
Node
)结合,实现了在大多数情况下无锁或低锁竞争的高性能并发访问,同时在哈希冲突严重时通过红黑树保证了性能的稳定性。这是一种非常精妙的设计,体现了并发编程的艺术。

在使用ConcurrentHashMap时,有哪些常见的陷阱或性能考量?

尽管ConcurrentHashMap功能强大且性能卓越,但在实际使用中,仍然有一些点需要注意,否则可能会遇到一些意料之外的行为或性能问题。

  1. 复合操作的非原子性: 虽然

    ConcurrentHashMap
    的
    put
    、
    get
    、
    remove
    等单个操作是线程安全的,但由这些操作组合而成的复合操作(例如
    get
    一个值,根据它计算一个新值,再
    put
    回去)并不是原子性的。 陷阱:

    // 假设多个线程同时执行这段代码,期望每次都递增1
    Integer value = map.get("counter");
    if (value == null) {
        map.put("counter", 1);
    } else {
        map.put("counter", value + 1); // 这里可能出现问题,两个线程同时get到旧值,导致只递增了一次
    }

    解决方案: 使用

    putIfAbsent
    、
    compute
    、
    merge
    这些原子性的复合操作。

    // 正确的递增方式
    map.compute("counter", (key, oldValue) -> oldValue == null ? 1 : oldValue + 1);
  2. 不允许null键或null值:

    ConcurrentHashMap
    和
    HashTable
    一样,不允许
    null
    作为键或值。这是为了避免歧义,因为
    get(key)
    返回
    null
    可能意味着键不存在,也可能意味着键存在但其值为
    null
    。 陷阱: 如果你不小心尝试
    put(null, value)
    或
    put(key, null)
    ,会直接抛出
    NullPointerException
    。 解决方案: 始终确保你的键和值是非
    null
    的。如果业务上需要表示“无值”,可以考虑使用
    Optional
    或特定的占位符对象。

  3. 迭代器的弱一致性:

    ConcurrentHashMap
    的迭代器是弱一致性的(weakly consistent),这意味着它不会抛出
    ConcurrentModificationException
    ,但在迭代过程中,如果其他线程修改了Map,迭代器可能不会反映这些修改,也可能部分反映。它反映的是在迭代器创建时或创建后某个时刻的映射状态。 陷阱: 如果你的业务逻辑强依赖于迭代时的数据快照,并且要求数据在迭代过程中不能有任何变化,那么弱一致性可能会导致问题。 解决方案: 如果需要一个严格的快照,你可能需要先将
    ConcurrentHashMap
    的内容复制到一个线程安全的集合中(例如
    new ArrayList<>(map.entrySet())
    ),然后迭代这个副本。当然,这会引入额外的内存和复制开销。对于大多数并发场景,弱一致性通常是可接受的。

  4. 初始容量与负载因子: 虽然

    ConcurrentHashMap
    在扩容方面做得很好,但如果你能预估Map的大小,并设置一个合理的初始容量(
    initialCapacity
    ),仍然可以减少扩容的次数,从而避免扩容带来的性能开销。 考量: 过小的初始容量会导致频繁扩容;过大的初始容量会浪费内存。通常,将其设置为你预计最大元素数量的两倍是一个不错的起点,因为
    ConcurrentHashMap
    的默认负载因子是0.75。

    // 假设你预计会有大约1000个元素
    ConcurrentHashMap<String, Data> myCache = new ConcurrentHashMap<>(1500); // 1500 * 0.75 约等于 1125
  5. 键的哈希性能:

    ConcurrentHashMap
    的性能高度依赖于键的
    hashCode()
    和
    equals()
    方法的实现。一个设计糟糕的哈希函数会导致大量的哈希冲突,使得大部分元素都集中在少数几个桶中,从而退化成链表或红黑树,降低查找效率。 考量: 确保你的自定义键类正确且高效地实现了
    hashCode()
    和
    equals()
    。一个好的哈希函数应该能将键均匀地分布在哈希空间中。

  6. 内存占用:

    ConcurrentHashMap
    为了实现线程安全和高并发,每个
    Node
    (或
    Entry
    )通常会比
    HashMap
    多一些字段(例如用于链表/红黑树的指针、哈希值等)。这会导致在存储大量小对象时,其内存占用会略高于
    HashMap
    。 考量: 在内存极其敏感的场景下,需要权衡并发性能和内存消耗。

理解这些点,可以帮助我们更自信、更高效地在项目中运用ConcurrentHashMap。它是一个强大的工具,但任何工具都有其最佳使用场景和需要注意的细节。

热门AI工具

更多
花生AI
花生AI Hot

一款AI视频创作工具,主要用于B站推出的AI视频创作工具,适合需要提升相关任务效率的用户。

Lovart
Lovart Hot

一款面向视觉设计创作的AI设计平台,可通过智能体和画布工作流辅助制作海报、Logo、网页、PPT及其他视觉内容。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

549

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1698

2024.03.01

c++中volatile关键字的作用
c++中volatile关键字的作用

本专题整合了c++中volatile关键字的相关内容,阅读专题下面的文章了解更多详细内容。

794

2025.10.23

C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

379

2026.04.23

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2721

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

376

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

417

2026.01.06

C++ 数据结构与算法实现教程合集
C++ 数据结构与算法实现教程合集

以 C++ 为实现语言,系统讲解核心数据结构与算法,涵盖链表(单链表/双链表/环检测)、栈与队列(单调栈/优先队列)、二叉树(遍历/BST/AVL/红黑树)、哈希表(开地址法/链地址法)、图(邻接表/BFS/DFS/Dijkstra/拓扑排序)、常见排序算法(快排/归并/堆排/计数排序)的实现与复杂度分析,同时分享 LeetCode 刷题技巧、竞赛编程常用模板(二分/前缀和/滑动窗口/动态规划),帮助开发者夯实算法基础。

472

2026.05.09

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

100

2026.10.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn