史上最全的Java容器集合之ConcurrentHashMap(源码解读)(上)

简介: 前言文本已收录至我的GitHub仓库,欢迎Star:github.com/bin39232820…种一棵树最好的时间是十年前,其次是现在

前言


文本已收录至我的GitHub仓库,欢迎Star:github.com/bin39232820…

种一棵树最好的时间是十年前,其次是现在

絮叨


HashMap讲完了,我们来看一下和他内部结构差不多的ConcurrentHashMap

🔥史上最全的Java容器集合之入门

🔥史上最全的Java容器集合之基础数据结构(手撕链表)

🔥史上最全的Java容器集合之ArrayList(源码解读)

🔥史上最全的Java容器集合之Vector和LinkedList(源码解读)

🔥史上最全的Java容器集合之equals 和 hashCode

🔥史上最全的Java容器集合之HashMap(源码解读)

本来Map家族还有一个TreeMap,但是自己对于红黑树还不是特别了解,就先放一放吧,因为用的也不多,ConcurrentHashMap可以解决线程安全问题,至于HashTable已经用的非常少了,不能为null,也是线程安全的。


ConcurrentHashMap


Tips:其实今天讲它肯定是大概的过一下,它既然是一个线程安全的容器,那么线程安全也要涉及到很多的知识点,比如

  • 悲观锁与乐观锁
  • 原子性,指令有序性和线程可见性
  • 无锁算法
  • 内存屏障
  • Java内存模型

这些目前等讲JVM的时候我们再一起去探讨吧,我们今天主要是过一下它,等后面把知识点串起来就会明白的。


跟HashTable 的区别,1.7和1.8的比较

ConcurrentHashMap是conccurrent家族中的一个类,由于它可以高效地支持并发操作,以及被广泛使用,经典的开源框架Spring的底层数据结构就是使用ConcurrentHashMap实现的。与同是线程安全的老大哥HashTable相比,它已经更胜一筹,因此它的锁更加细化,而不是像HashTable一样为几乎每个方法都添加了synchronized锁,这样的锁无疑会影响到性能。

1.7和1.8实现线程安全的思想也已经完全变了其中抛弃了原有的Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。它沿用了与它同时期的HashMap版本的思想,底层依然由“数组”+链表+红黑树的方式思想,但是为了做到并发,又增加了很多辅助的类,例如TreeBin,Traverser等对象内部类。


继承结构

跟HashMap就是一模一样


其实一个类是用来干嘛的再类的最开始的介绍是很详细的,但是我的渣渣英语水平,太难了,如果有能力的童鞋可以去好好看看。总结一下说了啥:

  • JDK1.8底层是散列表+红黑树
  • ConCurrentHashMap支持高并发的访问和更新,它是线程安全的
  • 检索操作不用加锁,get方法是非阻塞的
  • key和value都不允许为null


常量

//表的最大容量
  private static final int MAXIMUM_CAPACITY = 1 << 30;
  //默认表的容量
    private static final int DEFAULT_CAPACITY = 16;
  //最大数组长度
    static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  //默认并发级别
    private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
  //负载因子
    private static final float LOAD_FACTOR = 0.75f;
  //树化阈值
    static final int TREEIFY_THRESHOLD = 8;
  //去树化阈值
    static final int UNTREEIFY_THRESHOLD = 6;
  //树化的最小容量
    static final int MIN_TREEIFY_CAPACITY = 64;
  //转移的最小值
    private static final int MIN_TRANSFER_STRIDE = 16;
  //生成sizeCtl的最小位数
    private static int RESIZE_STAMP_BITS = 16;
  //进行扩容允许的最大线程数
    private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
  //sizeCtl所需要的偏移位数
    private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
  //标志值
    static final int MOVED     = -1; // hash for forwarding nodes
    static final int TREEBIN   = -2; // hash for roots of trees
    static final int RESERVED  = -3; // hash for transient reservations
    static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
  //cpu数量
    static final int NCPU = Runtime.getRuntime().availableProcessors();
  //序列化属性
    private static final ObjectStreamField[] serialPersistentFields = {
        new ObjectStreamField("segments", Segment[].class),
        new ObjectStreamField("segmentMask", Integer.TYPE),
        new ObjectStreamField("segmentShift", Integer.TYPE)
    };
复制代码


大部分的和HashMap差不多,只有少数的不同


成员变量

//node数组,第一次插入节点时初始化 都加了内存屏障
    transient volatile Node<K,V>[] table;
     //用于扩容
    private transient volatile Node<K,V>[] nextTable;
     //计算器值,通过CAS修改值,没有竞争时使用,或者出现多线程初始化时回滚
    private transient volatile long baseCount;
     //初始化和扩容的标志,concurrent包中有很多类似用法
     // -1 初始化中; -N (N-1)个线程在扩容;table没有数据 初始化的大小 ; table有数据 下一次扩容的大小
  // 非常重要的一个属性,源码中的英文翻译,直译过来是下面的四行文字的意思
  //     sizeCtl = -1,表示有线程正在进行真正的初始化操作
  //     sizeCtl = -(1 + nThreads),表示有nThreads个线程正在进行扩容操作
  //     sizeCtl > 0,表示接下来的真正的初始化操作中使用的容量,或者初始化/扩容完成后的threshold
  //     sizeCtl = 0,默认值,此时在真正的初始化操作中使用默认容量
    //sizeCtl = -(1 + nThreads)这个,网上好多都是用第二句的直接翻译去解释代码,这样理解是错误的
  // 默认构造的16个大小的ConcurrentHashMap,只有一个线程执行扩容时,sizeCtl = -2145714174,
  //     但是照这段英文注释的意思,sizeCtl的值应该是 -(1 + 1) = -2
  // sizeCtl在小于0时的确有记录有多少个线程正在执行扩容任务的功能,但是不是这段英文注释说的那样直接用 -(1 + nThreads)
  // 实际中使用了一种生成戳,根据生成戳算出一个基数,不同轮次的扩容操作的生成戳都是唯一的,来保证多次扩容之间不会交叉重  叠,
  //     当有n个线程正在执行扩容时,sizeCtl在值变为 (基数 + n)
  // 1.8.0_111的源码的383-384行写了个说明:A generation stamp in field sizeCtl ensures that resizings do not overlap.
    private transient volatile int sizeCtl;
    /**
     * The next table index (plus one) to split while resizing.
     */
     //transfer的table索引
    private transient volatile int transferIndex;
     //扩容或创建counterCells的自旋锁
    private transient volatile int cellsBusy;
     // CounterCell数组
    private transient volatile CounterCell[] counterCells;
    // views
    private transient KeySetView<K,V> keySet;
    private transient ValuesView<K,V> values;
    private transient EntrySetView<K,V> entrySet;
复制代码


这里重点解释一下sizeCtl这个属性。可以说它是ConcurrentHashMap中出镜率很高的一个属性,因为它是一个控制标识符,在不同的地方有不同用途,而且它的取值不同,也代表不同的含义。


  • 负数代表正在进行初始化或扩容操作
  • -1代表正在初始化
  • -N 表示有N-1个线程正在进行扩容操作
  • 正数或0代表hash表还没有被初始化,这个数值表示初始化或下一次进行扩容的大小,这一点类似于扩容阈值的概念。还后面可以看到,它的值始终是当前ConcurrentHashMap容量的0.75倍,这与loadfactor是对应的。


构造方法

其实和HashMap是大同小异的,此时ConcurrentHashMap的构造方法逻辑和HashMap基本一致,只是多了concurrencyLevel和SizeCtl。 而且此时也没有初始化table,它是要等到第一次put的时候才初始化table,


相关文章
|
8月前
|
Java 虚拟化 容器
(Java)Java里JFrame窗体的基本操作(容器布局篇-1)
容器 容器,我的理解是可以包容其他东西的玩意。它可以是一个盒子,可以是一个虚拟化的物品,可只要能包裹住其他存在质体的东西,那么都可以称作是容器。例如:JPanel组件和JScollPane组件两者都是容器也是组件。 既然有容器,那么容器中的布局就必不可少了。不然不规矩的摆放物品,人类看不习惯,我也看不习惯 ???? 本篇内容,将说明java JFrame窗体里容器中几类布局。 说明:所有在JFrame窗体里的容器布局都会使用setLayout()方法,采用的布局参数都将放进这个方法里 绝对布局 调用窗体容器
229 1
|
9月前
|
Java 大数据 API
Java Stream API:现代集合处理与函数式编程
Java Stream API:现代集合处理与函数式编程
401 100
|
9月前
|
Java API 数据处理
Java Stream API:现代集合处理新方式
Java Stream API:现代集合处理新方式
385 101
|
9月前
|
算法 Java
50道java集合面试题
50道 java 集合面试题
|
8月前
|
存储 算法 安全
Java集合框架:理解类型多样性与限制
总之,在 Java 题材中正确地应对多样化与约束条件要求开发人员深入理解面向对象原则、范式编程思想以及JVM工作机理等核心知识点。通过精心设计与周密规划能够有效地利用 Java 高级特征打造出既健壮又灵活易维护系统软件产品。
223 7
|
10月前
|
存储 缓存 安全
Java集合框架(二):Set接口与哈希表原理
本文深入解析Java中Set集合的工作原理及其实现机制,涵盖HashSet、LinkedHashSet和TreeSet三大实现类。从Set接口的特性出发,对比List理解去重机制,并详解哈希表原理、hashCode与equals方法的作用。进一步剖析HashSet的底层HashMap实现、LinkedHashSet的双向链表维护顺序特性,以及TreeSet基于红黑树的排序功能。文章还包含性能对比、自定义对象去重、集合运算实战和线程安全方案,帮助读者全面掌握Set的应用与选择策略。
1176 23
|
9月前
|
存储 Java Go
对比Java学习Go——函数、集合和OOP
Go语言的函数支持声明与调用,具备多返回值、命名返回值等特性,结合`func`关键字与类型后置语法,使函数定义简洁直观。函数可作为一等公民传递、赋值或作为参数,支持匿名函数与闭包。Go通过组合与接口实现面向对象编程,结构体定义数据,方法定义行为,接口实现多态,体现了Go语言的简洁与高效设计。
273 4
|
10月前
|
存储 缓存 安全
Java集合框架(三):Map体系与ConcurrentHashMap
本文深入解析Java中Map接口体系及其实现类,包括HashMap、ConcurrentHashMap等的工作原理与线程安全机制。内容涵盖哈希冲突解决、扩容策略、并发优化,以及不同Map实现的适用场景,助你掌握高并发编程核心技巧。
|
9月前
|
存储 小程序 Java
热门小程序源码合集:微信抖音小程序源码支持PHP/Java/uni-app完整项目实践指南
小程序已成为企业获客与开发者创业的重要载体。本文详解PHP、Java、uni-app三大技术栈在电商、工具、服务类小程序中的源码应用,提供从开发到部署的全流程指南,并分享选型避坑与商业化落地策略,助力开发者高效构建稳定可扩展项目。
|
10月前
|
存储 NoSQL Java
Java Stream API:集合操作与并行处理
Stream API 是 Java 8 提供的集合处理工具,通过声明式编程简化数据操作。它支持链式调用、延迟执行和并行处理,能够高效实现过滤、转换、聚合等操作,提升代码可读性和性能。