专栏首页JavaEdge深入理解并发容器-ConcurrentHashMap(JDK8版本)1 概述3应用场景4 源码解析

深入理解并发容器-ConcurrentHashMap(JDK8版本)1 概述3应用场景4 源码解析

1 概述

集合是编程中最常用的数据结构。而谈到并发,几乎总是离不开集合这类高级数据结构的支持。比如两个线程需要同时访问一个中间临界区(Queue),比如常会用缓存作为外部文件的副本(HashMap)。这篇文章主要分析Java5的3种并发集合类型(concurrent,copyonright,queue)中的ConcurrentHashMap,让我们从原理上细致的了解它们,能够让我们在项目开发中获益非浅,顺便斩获名企offer. 有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁.这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证.这可以确保不会出现死锁,因为获得锁的顺序是固定的. ConcurrentHashMap是conccurrent家族中的一个类,由于它可以高效地支持并发操作,以及被广泛使用,开源框架Spring的底层数据结构就是使用ConcurrentHashMap实现的。与同是线程安全的老大哥HashTable相比,它更胜一筹,因为它的锁更加细化,而不是像HashTable一样为几乎每个方法都添加了synchronized锁,这样的锁无疑会影响到性能。 本文的分析的源码是Java8版,与Java6版有很大的差异。实现线程安全的思想也已经完全变了,它摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现,利用CAS算法。它沿用了与它同时期的HashMap版本的思想,底层依然由“数组”+链表+红黑树的方式思想,但是为了做到并发,又增加了很多辅助的类,例如TreeBin,Traverser等内部类。

3应用场景

当有一个大数组时需要在多个线程共享时就可以考虑是否把它给分层多个节点了,避免大锁.并可以考虑通过hash算法进行一些模块定位. 其实不止用于线程,当设计数据表的事务时(事务某种意义上也是同步机制的体现),可以把一个表看成一个需要同步的数组,如果操作的表数据太多时就可以考虑事务分离了(这也是为什么要避免大表的出现),比如把数据进行字段拆分,水平分表等.

4 源码解析

重要的属性

首先来看几个重要的属性,与HashMap相同的就不再介绍了,这里重点解释一下sizeCtl这个属性。可以说它是ConcurrentHashMap中出镜率很高的一个属性,因为它是一个控制标识符,在不同的地方有不同用途,而且它的取值不同,也代表不同的含义。

 /**
     * 在以前的版本中使用的辅助类的精简版
     * 由于序列化兼容的缘故而声明
     */
    static class Segment<K,V> extends ReentrantLock implements Serializable {
        private static final long serialVersionUID = 2249069246763182397L;
        //负载因子
        final float loadFactor;
        Segment(float lf) { this.loadFactor = lf; }
    }
/**
     The array of bins. Lazily initialized upon first insertion.
     Accessed directly by iterators.
     * 盛装Node元素的数组 大小永远是2的整数次幂 
     */
    transient volatile Node<K,V>[] table;
   
    /**
    当 table 为空,sizectl 保存初始的 table 大小,用于创建新的 table,默认为 0。    在初始化后,这个变量保存的值的含义是下一次需要 resize table 的元素数量,相当于阈值。元素超过这个阈值,table 就要 resize
     * 表初始化和扩容时的控制位标识量。
     * 负数代表正在进行初始化或扩容操作 
     * -1代表正在初始化 
     * -N 表示有N-1个线程正在进行扩容操作 
     * 正数或0代表哈希表还没有被初始化,这个数值表示初始化或下一次进行扩容的大小 
     */
    private transient volatile int sizeCtl;

   // 以下两个是用来控制扩容的时候 单线程进入的变量  
   /** 
   * The number of bits used for generation stamp in sizeCtl. 
   * Must be at least 6 for 32bit arrays. 
   */  
  private static int RESIZE_STAMP_BITS = 16;  
   
   /** 
   * The bit shift for recording size stamp in sizeCtl. 
   */  
  private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;  
    
    
  /* 
   * Encodings for Node hash fields. See above for explanation. 
   */  
  static final int MOVED     = -1; // hash值是-1,表示这是一个forwardNode节点  
  static final int TREEBIN   = -2; // hash值是-2  表示这时一个TreeBin节点  

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 长文慎入-探索Java并发编程与高并发解决方案(更新中)1 基本概念2 CPU3 项目准备4线程安全性5发布对象7 AQS9 线程池10 死锁

    JavaEdge
  • 掌握 @transactional 注解@Transactional 注解管理事务的实现步骤Spring 的注解方式的事务实现机制

    JavaEdge
  • 虚拟机的类加载机制1 类加载的时机

    JavaEdge
  • JDK动态代理代理与Cglib代理原理探究

    UserServiceImpl被JDK代理后的类,在项目的com.sun.proxy下面生成$Proxy0.class类

    加多
  • 大规模微服务场景下灰度发布与流量染色实践

    本文内容选自中国DevOps社区年会 · 2019年会,刘超老师分享的《大规模微服务场景下灰度发布与流量染色实践》实录。

    kirito-moe
  • 理解Java并发工具类Semaphore

    Semaphore是Java里面另外一个基本的并发工具包类,主要的的作用是用来保护共享资源的访问的,也就是仅仅允许一定数量的线程访问共享资源。Semaphore...

    我是攻城师
  • 网络爬虫暗藏杀机:在Scrapy中利用Telnet服务LPE

    网络抓取框架中使用最多的莫过于是scrapy,然而我们是否考虑过这个框架是否存在漏洞妮?5年前曾经在scrapy中爆出过XXE漏洞,然而这次我们发现的漏洞是一个...

    FB客服
  • ehcache报错

    jfinal2.0+tomcat7+ehcache2.6.11+Linux Linux version 2.6.18-164.el5 (mockbuild@x8...

    Ryan-Miao
  • 记一次JAVA进程导致Kubernetes节点CPU飙高的排查与解决

    在一次系统上线后,我们发现某几个节点在长时间运行后会出现CPU持续飙升的问题,导致的结果就是Kubernetes集群的这个节点会把所在的Pod进行驱逐(调度);...

    yoyofx
  • CPU占用100%排查过程

    https://blog.csdn.net/zxh87/article/details/52137335

    spilledyear

扫码关注云+社区

领取腾讯云代金券