首页 > Java > java教程 > 正文

Java中ConcurrentHashMap的使用方法

P粉602998670
发布: 2025-09-19 13:05:01
原创
659人浏览过
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
登录后复制

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

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

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

启科网络PHP商城系统
启科网络PHP商城系统

启科网络商城系统由启科网络技术开发团队完全自主开发,使用国内最流行高效的PHP程序语言,并用小巧的MySql作为数据库服务器,并且使用Smarty引擎来分离网站程序与前端设计代码,让建立的网站可以自由制作个性化的页面。 系统使用标签作为数据调用格式,网站前台开发人员只要简单学习系统标签功能和使用方法,将标签设置在制作的HTML模板中进行对网站数据、内容、信息等的调用,即可建设出美观、个性的网站。

启科网络PHP商城系统 0
查看详情 启科网络PHP商城系统

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。它是一个强大的工具,但任何工具都有其最佳使用场景和需要注意的细节。

以上就是Java中ConcurrentHashMap的使用方法的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号