深入理解并发容器-ConcurrentHashMap(JDK8版本)

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 1 概述集合是编程中最常用的数据结构。而谈到并发,几乎总是离不开集合这类高级数据结构的支持。比如两个线程需要同时访问一个中间临界区(Queue),比如常会用缓存作为外部文件的副本(HashMap)。

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节点  


目录
相关文章
|
3月前
|
Java Linux 开发工具
Jenv 多版本JDK管理
Jenv 可以对多版本JDK进行管理
|
4月前
|
Web App开发 Java Linux
Nexus【部署 02】最新版本 nexus-3.35.0-02-unix.tar.gz 安装配置启动及测试(JDK版本+虚拟机参数配置说明)
Nexus【部署 02】最新版本 nexus-3.35.0-02-unix.tar.gz 安装配置启动及测试(JDK版本+虚拟机参数配置说明)
116 0
|
4月前
|
存储 Java 编译器
JVM-不同jdk版本静态变量存储位置
JVM-不同jdk版本静态变量存储位置
|
3月前
|
Oracle JavaScript Java
JDK的版本迭代特性(JDK9 - JDK20)
JDK的版本迭代特性(JDK9 - JDK20)
|
2月前
|
Oracle Java 编译器
基本概念【入门、 发展简史、核心优势、各版本的含义、特性和优势、JVM、JRE 和 JDK 】(二)-全面详解(学习总结---从入门到深化)
基本概念【入门、 发展简史、核心优势、各版本的含义、特性和优势、JVM、JRE 和 JDK 】(二)-全面详解(学习总结---从入门到深化)
46 1
|
2天前
|
Oracle Java 关系型数据库
Java 开发者必备:JDK 版本详解与选择策略(含安装与验证)
Oracle Java SE 支持路线图显示,JDK 8(LTS)支持至2030年,非LTS版本如9-11每6个月发布且支持有限。JDK 11(LTS)支持至2032年,而JDK 17及以上版本现在提供免费商用许可。LTS版本提供长达8年的支持,每2年发布一次。Oracle JDK与OpenJDK有多个社区和公司构建版本,如Adoptium、Amazon Corretto和Azul Zulu,它们在许可证、商业支持和更新方面有所不同。个人选择JDK时,可考虑稳定性、LTS、第三方兼容性和提供商支持。
6 0
|
4月前
|
Java Maven
[Java ] jdk升级 bug java: -source 8 中不支持 instanceof 中的模式匹配 (请使用 -source 16 或更高版本以启用 instanceof 中的模式匹配)
[Java ] jdk升级 bug java: -source 8 中不支持 instanceof 中的模式匹配 (请使用 -source 16 或更高版本以启用 instanceof 中的模式匹配)
133 0
|
29天前
|
Java 应用服务中间件 nginx
idea打war包时,JDK版本的问题解决方式
idea打war包时,JDK版本的问题解决方式
13 0
|
1月前
|
安全 Java API
Java并发 - J.U.C并发容器类 list、set、queue
Queue API 阻塞是通过 condition 来实现的,可参考 Java 并发 - Lock 接口 ArrayBlockingQueue 阻塞 LinkedBlockingQueue 阻塞 ArrayQueue 非阻塞 LinkedQueue 非阻塞
|
5月前
|
存储 Linux Docker
跨cpu架构部署容器技术点:怎样修改Linux 的内核版本
在使用Docker 进行跨平台部署之后,我们还可以尝试进行跨架构部署。 从X86 架构上移植到 aarch64 上。
199 0