ConcurrentHashMap的使用介绍和底层原理解析和开源框架的使用实例

news/2024/5/20 5:47:40 标签: java, 并发编程, juc, 哈希算法

文章目录

  • ConcurrentHashMap的使用介绍和底层原理解析和开源框架的使用实例
    • 1. ConcurrentHashMap介绍
    • 2. ConcurrentHashMap底层原理
    • 3. ConcurrentHashMap主要方法- put(K key, V value):添加元素。
    • 4. 总结
    • 5. 框架中的应用
    • 6. 操作技巧
    • 7. 运维部署(生产环境注意事项)
    • 8. ConcurrentHashMap扩展-JDK8改进
    • 9. 总结

ConcurrentHashMap的使用介绍和底层原理解析和开源框架的使用实例

1. ConcurrentHashMap介绍

ConcurrentHashMap是JDK1.5提供的线程安全的HashMap,它允许多个线程并发访问哈希表,并发修改map中的数据而不会产生死锁。ConcurrentHashMap适用于高并发的环境下,可以替代synchronized实现的同步HashMap。ConcurrentHashMap的并发度很高,吞吐量也很高。

2. ConcurrentHashMap底层原理

ConcurrentHashMap底层采用“分段锁”机制,将数据分成一段段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问,

能够实现真正的并发访问。ConcurrentHashMap结构如下:

  • Segment数组:存放数据段,默认16个段,数组大小始终为2的幂次方。

  • 每个Segment是一把锁,锁定一个段数据的所有访问。

  • 每个Segment包含一个HashEntry数组,用来存储链表结构的数据。

  • 一个HashEntry就是一个节点,存储key-value键值对。

3. ConcurrentHashMap主要方法- put(K key, V value):添加元素。

  • get(K key):获取元素。
  • size():返回ConcurrentHashMap的大小。
  • isEmpty():判断ConcurrentHashMap是否为空。这些方法都可以在多线程环境下调用,方法内部会处理线程安全问题。示例代码:
java">ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

 // 添加元素
 map.put("a", 1);
 map.put("b", 2);
 map.put("c", 3);

 // 获取元素
 Integer a = map.get("a");
 Integer b = map.get("b");
 Integer c = map.get("c");

 // 大小和判断是否为空
 int size = map.size(); 
 boolean empty = map.isEmpty(); 

4. 总结

ConcurrentHashMap通过锁分段技术,实现高度的并发访问,大大提高了HashMap的吞吐量,是高并发环境下一个很好的选择。理解ConcurrentHashMap的原理和结构,可以更好的发挥其高性能特点。

5. 框架中的应用

ConcurrentHashMap在很多开源框架中广泛应用,这里举两个例子:

  1. Spring Cache 注解 @Cacheable 的底层缓存存储就是采用ConcurrentHashMap来实现的。Spring Cache 对象存储在 ConcurrentHashMap<name, Cache> 中,name为缓存名称。
  2. Mybatis映射 SqlSessionFactory 里面的Configuration对象的mappedStatements属性就是一个ConcurrentHashMap。它的key是statement id, value是封装好的映射语句MappedStatement对象。

这两个例子都采用ConcurrentHashMap来存放数据,体现了它的 thread-safe 特性,可以在高并发场景下安全地操作数据。

6. 操作技巧

在开发中,我们也要注意ConcurrentHashMap的一些操作技巧:

  1. 初始化大小最好是2的幂次方,默认是16,可以根据实际数据量选择合适大小,此可以减少rehash的次数,提高效率。
  2. 如果需要Iterator遍历,最好使用entrySet来遍历Map。因为如果在遍历的过程中,Map的数据发生了变化(增加、删除元素),迭代器并不会抛出ConcurrentModificationException异常。
  3. 在计算ConcurrentHashMap的size()时,如果此时有其他线程正在进行添加/删除操作,计算出的size值可能是不准确的。如果需要精确的size值,可使用mappingCount()方法。
  4. 如果希望ConcurrentHashMap中的key或value组成固定顺序,可以使用TreeMap。ConcurrentHashMap的Key-Value是无序的。
  5. 在使用ConcurrentHashMap的过程中,如果遇到元素添加或删除较慢的情况,应考虑map的容量是否过小,是否需要扩容。扩容会带来性能消耗。

示例代码:

java">// 初始化大小
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>(32);

// entrySet遍历 
for (Map.Entry<String, String> entry : map.entrySet()) {
    String key = entry.getKey();
    String value = entry.getValue();
    // do something
}

// mappingCount()计算精确大小
int size = map.mappingCount();

7. 运维部署(生产环境注意事项)

在实际项目中,ConcurrentHashMap的使用也需要考虑一些运维方面的内容:

  1. 监控ConcurrentHashMap的大小,避免OOM。ConcurrentHashMap容量过大会导致OOM,需要监控map的size,一旦超过阈值需要考虑清理旧数据或扩容map。
  2. 关注CPU使用率和负载。ConcurrentHashMap高并发会导致CPUUsage和负载升高,需要及时监控和调优。可以通过调大初始容量、扩容更加缓慢、reduce锁粒度等手段优化。
  3. GC频率监控。高并发下,ConcurrentHashMap会产生大量临时对象,导致GC频繁,GC时间长会影响系统性能,需要关注老年代GC时间和频率,必要时进行GC优化。
  4. 问题诊断工具。ConcurrentHashMap底层采用“分段锁”机制,如果使用不当可以产生死锁。需要熟悉如jstack等诊断工具,及时发现死锁问题并解决。
  5. 负载均衡。在高并发下,如果ConcurrentHashMapbottleneck,需要考虑尽量分散压力,可以采取加机器、分布式相关手段进行负载均衡。
  6. 测试Verification。高并发场景下ConcurrentHashMap性能表现和同步Map相比会更加复杂,需要进行充分的性能测试,判断是否达到预期效果。修改ConcurrentHashMap的capacity、segment数、rehash等策略都需要充分评估和验证。

这些运维方面内容,可以让ConcurrentHashMap在生产环境中运行更加稳定可靠。总之,ConcurrentHashMap是JDK提供的高性能Map实现,但在实际生产环境中,依然需要运维团队投入大量时间去监控、诊断和优化才能发挥其最高性能。

8. ConcurrentHashMap扩展-JDK8改进

在JDK8中,ConcurrentHashMap进行了较大改进,比较重要的有两点:

  1. 采用CAS操作替换重量级锁,降低锁粒度,实现更高的并发度。
  2. 采用红黑树替换链表,提高查询效率。具体改进如下:
  • Segment改为Node,每个Node是一个链表结构的首节点。不再分段锁定,采用CAS操作同步机制。
  • 采用 volatile + CAS 操作线程安全地修改节点,代替重量级锁。
  • 链表长度超过8自动转换为红黑树,提高查询效率。节点采用二叉查找树结构。
  • size属性和mappingCount方法删除,采用遍历计数的方式统计大小。
  • put方法不再加锁,采用CAS操作,删除“死循环”逻辑。
  • 初始化大小和扩容机制改进。默认大小为16,扩容为原来的2倍。

改进代码示例:

java">static final int MIN_TREEIFY_CAPACITY = 64;  //链表转换红黑树阈值

/**
 * Initializes or doubles table size.  If null, allocates in
 * accord with initial capacity target held in field.  Otherwise,
 * because we are using power-of-two expansion, the elements from
 * each bin must either stay at same index, or move with a power
 * of two offset in the new table.
 *
 * @return the table
 */
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        // 超过最大值就不再扩充了,就只好随你碰撞去吧
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 没超过最大值,就扩充为原来的2倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= MIN_TREEIFY_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // 计算新的resize上限
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    ......
}

可以看到JDK8在ConcurrentHashMap扩容和查询等机制上进行了比较大的改进,效率更高,是生产环境中更好的选择。

9. 总结

ConcurrentHashMap是Java中高性能的线程安全Map实现,通过锁分段技术实现高度并发。用它来替代同步的HashMap可以大大提高性能。

本文主要内容如下:

  1. ConcurrentHashMap介绍及特点。
  2. ConcurrentHashMap的内部结构和原理剖析。采用锁分段技术实现线程安全和高并发。
  3. ConcurrentHashMap的主要方法和示例代码。
  4. 框架和生产环境中的应用实例。如Spring Cache和Mybatis中广泛应用。
  5. ConcurrentHashMap操作技巧与性能优化手段。合理初始化、遍历方式选择、大小计算等。
  6. ConcurrentHashMap运维部署与监控。容量控制、CPU和GC监控、问题诊断和解决等。
  7. JDK8对ConcurrentHashMap的改进。采用CAS和红黑树替换锁和链表,实现更高效的并发度和查询性能。

ConcurrentHashMap是一个复杂而高性能的组件,要充分理解其原理和机制,并在生产环境中结合运维知识进行监控和优化,才能发挥其最大效能。

希望本文能帮助大家充分理解ConcurrentHashMap的设计思想和实现机制。ConcurrentHashMap的高性能特性配合良好的运维手段,可以使系统整体吞吐量大幅增加,是高并发环境下一个很好的选择。


http://www.niftyadmin.cn/n/280935.html

相关文章

权限提升:烂土豆 || DLL 劫持.

权限提升&#xff1a;烂土豆 || DLL 劫持. 权限提升简称提权&#xff0c;由于操作系统都是多用户操作系统&#xff0c;用户之间都有权限控制&#xff0c;比如通过 Web 漏洞拿到的是 Web 进程的权限&#xff0c;往往 Web 服务都是以一个权限很低的账号启动的&#xff0c;因此通…

给jar包编写start和stop脚本

文章目录 前言一、编写脚本1.start.sh2.stop.sh3.restart.sh 二、展示总结 前言 springboot项目内置tomcat,一般都是以jar包形式对外发布服务,我们不能每次都去kill pid,抽到脚本里来做这个事会方便许多。 一、编写脚本 1.start.sh #!/bin/bash APP_NAME"springboot2.3…

Mysql数据库的存储引擎——新手必看教程

文章目录 目录 文章目录 一、什么是存储引擎 二、MySQL支持的存储引擎 三、常见的存储引擎 1.InnoDB存储引擎 2.MyISAM存储引擎 3.MEMORY存储引擎 四、选择存储引擎 总结 一、什么是存储引擎 MySQL中存在多种存储引擎的概念。简而言之&#xff0c;存储引擎就是指表的类型。在…

Spring 统一功能处理(拦截器)

文章目录 Spring拦截器1.统一用户登录权限校验1) SpringAOP 用户统一验证的问题2) Spring拦截器3) 拦截器实现原理4&#xff09;同一访问前缀添加 2. 统一异常处理3. 统一数据返回格式1&#xff09;统一数据返回的好处2&#xff09;统一数据返回实现 Spring拦截器 SpringBoot统…

如何在fastadmin的html模板中,循环一段自定义表单模板代码?

在FastAdmin的HTML模板中&#xff0c;可以使用模板引擎语法和JavaScript代码来实现自定义表单模板代码的循环。具体步骤如下&#xff1a; 在HTML模板中&#xff0c;定义一个占位符&#xff0c;用来显示循环后的自定义表单模板代码。例如&#xff1a; <div id"custom-fo…

【14.HTML-移动端适配】

移动端适配 1 布局视口和视觉视口1.1 设置移动端布局视口宽度 2 移动端适配方案2.1 rem单位动态html的font-size&#xff1b;2.2 vw单位2.3 rem和vw对比2.4 flex的弹性布局 1 布局视口和视觉视口 1.1 设置移动端布局视口宽度 避免布局视口宽度默认980px带了的缩放问题,并且禁止…

基于深度学习的水果检测与识别系统(Python界面版,YOLOv5实现)

摘要&#xff1a;本博文介绍了一种基于深度学习的水果检测与识别系统&#xff0c;使用YOLOv5算法对常见水果进行检测和识别&#xff0c;实现对图片、视频和实时视频中的水果进行准确识别。博文详细阐述了算法原理&#xff0c;同时提供Python实现代码、训练数据集&#xff0c;以…

synchronized用法加锁原理

目录 使用场景不同场景加锁对象结论验证实验实验1&#xff1a; synchronized 修饰方法&#xff0c;加锁对象是类实例&#xff0c;不同实例之间的锁互不影响实验2&#xff1a; synchronized 加在静态方法上&#xff0c;加锁对象是方法所在类&#xff0c;不同类实例之间相互影响实…