前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ConcurrentHashMap的使用介绍和底层原理解析和开源框架的使用实例

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

原创
作者头像
青山师
发布2023-05-02 12:11:16
6590
发布2023-05-02 12:11:16
举报

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

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

本文主要内容如下:

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

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扩容和查询等机制上进行了比较大的改进,效率更高,是生产环境中更好的选择。

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

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ConcurrentHashMap的使用介绍和底层原理解析和开源框架的使用实例
    • 1. ConcurrentHashMap介绍
      • 2. ConcurrentHashMap底层原理
        • 3. ConcurrentHashMap主要方法- put(K key, V value):添加元素。
          • 4. 总结
            • 5. 框架中的应用
              • 6. 操作技巧
                • 7. 运维部署(生产环境注意事项)
                  • 8. ConcurrentHashMap扩展-JDK8改进
                  相关产品与服务
                  负载均衡
                  负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档