ConcurrentHashMap详解

2023-10-27

目录

ConcurrentHashMap介绍

ConcurrentHashMap底层数据结构

ConcurrentHashMap部分分析

ConcurrentHashMap与HashMap、HashTable的区别


源码为jdk1.7

ConcurrentHashMap介绍

     ConcurrentHashMap 是concurrent包下的一个集合类。它是线程安全的哈希表。它是通过“分段锁”来实现多线程下的安全问题。它将哈希表分成了不同的段内使用了可重入锁(ReentrantLock ),不同线程只在一个段内存在线程的竞争。它不会对整个哈希表加锁。

public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
        implements ConcurrentMap<K, V>, Serializable {

它继承了AbstractMap类,实现了ConcurrentMap,Serializable接口

public abstract class AbstractMap<K,V> implements Map<K,V> {

可以看出AbstractMap类继承了Map接口,也就是说存放着键值对,还有实现了Map接口那些方法。

ConcurrentHashMap底层数据结构

ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap的结构。它内部拥有一个HashEntry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

 static final class Segment<K,V> extends ReentrantLock implements Serializable {

Segment继承了可重入锁。

static final class HashEntry<K,V> {
        final K key;
        final int hash;
        volatile V value;
        final HashEntry<K,V> next;

        HashEntry(K key, int hash, HashEntry<K,V> next, V value) {
            this.key = key;
            this.hash = hash;
            this.next = next;
            this.value = value;
        }

	@SuppressWarnings("unchecked")
	static final <K,V> HashEntry<K,V>[] newArray(int i) {
	    return new HashEntry[i];
	}
    }

HashEntry这个类有key、value、next、hash四个属性。HashEntry中的value被volatile修饰,这样在多线程读写过程中能够保持它们的可见性。

ConcurrentHashMap部分分析

ConcurrentHashMap具有的属性

    //默认容量大小
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    //默认加载因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //默认并发数量(segments数组的大小)
    static final int DEFAULT_CONCURRENCY_LEVEL = 16;
    //最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //最大并发数量
    static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
    static final int RETRIES_BEFORE_LOCK = 2;
    final int segmentMask;
    final int segmentShift;
    //segments数组
    final Segment<K,V>[] segments;
    //迭代器相关
    transient Set<K> keySet;
    transient Set<Map.Entry<K,V>> entrySet;
    transient Collection<V> values;

构造函数 

public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        //参数的有效性判断
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        //如果concurrencyLevel大于最大并发量,将其值设为最大并发量。(segments数组的大小)
        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;

        // Find power-of-two sizes best matching arguments
        int sshift = 0;
        int ssize = 1;
        //sshift记录ssize左移的次数
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        segmentShift = 32 - sshift;
        segmentMask = ssize - 1;
        this.segments = Segment.newArray(ssize);

        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //c为每个为table数组分配的容量
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = 1;
        while (cap < c)
            cap <<= 1;

        for (int i = 0; i < this.segments.length; ++i)
            this.segments[i] = new Segment<K,V>(cap, loadFactor);
    }

 concurrencyLevel的作用就是用来计算segments数组的容量大小。先计算出“大于或等于concurrencyLevel的最小的2的N次方值”,然后将其保存为“segments的容量大小(ssize)”。

put()方法

public V put(K key, V value) {
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key.hashCode());
        return segmentFor(hash).put(key, hash, value, false);
    }

如果value为null,抛出异常。不允许值为空。通过key得到hash值。将key-value键值对添加到Segment片段中。

 final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            //尝试获得锁,如果失败则通过scanAndLockForPut方法获得锁
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                HashEntry<K,V>[] tab = table;
            //  根据”hash值“获取”HashEntry数组中对应的HashEntry链表“
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> first = entryAt(tab, index);
                for (HashEntry<K,V> e = first;;) {
                    if (e != null) {
                        K k;
                        //如果key重复,则覆盖旧值
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                    //否则创建新的HashEntry节点
                        if (node != null)
                            node.setNext(first);
                        else
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                    //如果容量大于阈值,需要重hash
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
            //解锁
                unlock();
            }
            return oldValue;
        }

put()的作用是将key-value键值对插入到“当前Segment对应的HashEntry中”,在插入前它会获取Segment对应的互斥锁,插入后会释放锁。

rehash()的作用是将”Segment的容量“变为”原始的Segment容量的2倍“。
在将原始的数据拷贝到“新的Segment”中后,会将新增加的key-value键值对添加到“新的Segment”中。

get方法

获取key值对应的hash值,在对应的segment里面获取 。

public V get(Object key) {
    Segment<K,V> s;
    HashEntry<K,V>[] tab;
    int h = hash(key);
    long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
    if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
        (tab = s.table) != null) {
        for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                 (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
             e != null; e = e.next) {
            K k;
            if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                return e.value;
        }
    }
    return null;
}
 
 

可以看到get方法并没有加锁

get(Object key)的作用是返回key在ConcurrentHashMap里的值。首先根据key计算出hash值,获取key对应的Segment片段。如果Segment不为null,则在Segment片段的HashEntry链表中遍历这个链表找到对应的HashEntry节点。

由于遍历过程中其他线程可能对链表结构做了调整,因此get和containsKey返回的可能是过时的数据,这一点是ConcurrentHashMap在弱一致性上的体现。

ConcurrentHashMap与HashMap、HashTable的区别

  1. ConcurrentHashMap 、HashTable不允许值为null,值为null时抛出异常,HashMap允许值为null
  2. HashTable所有方法都加锁synchronized,为线程安全的。HashMap不保证线程安全。ConcurrentHashMap采用了分段锁,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。
  3. ConcurrentHashMap为弱一致性,在get方法时有可能获得过时的数据。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ConcurrentHashMap详解 的相关文章

  • 手把手带你修复老照片

    你家里是否有很多带着故事的老照片呢 随着时间的流逝 这些照片难免会变模糊 或者有了划痕 今天给大家介绍一种使用程序修复老照片的方法 教程面向小白 对于有基础的人过程可能略显繁琐 修复效果如下图所示 我们需要在电脑中下载安装程序运行环境 修复
  • 【学习笔记】电机学

    电枢的感生电动势 E a C e n

随机推荐

  • C++Primer(第五版 )第十一章 关联容器 章节编程练习答案

    11 1 描述map和vector的不同 答 map 是关联容器 vector 是顺序容器 11 2 分别给出最适合使用list vector deque map以及set的例子 答 list 双向链表 适合频繁插入删除元素的场景 vect
  • 关于 0.1+0.2 == 0.3 不成立的一些细节

    很早之前看到一关于js的问题 如下 实际上 0 1 0 2 0 3 这个问题不是js特有 来看一段java代码 Test public strictfp void test System out println 0 1f 0 2f 0 3f
  • 记random的几个函数用法及区别:random(),randint(),randrange(),uniform()

    1 random random 作用 生成 0 0 1 0 之间的随机小数 注意 不包含1 0 参数 无 gt gt gt from random import gt gt gt seed 10 gt gt gt random 0 5714
  • 三款好用的软件代码检测工具

    Fortify 是一款由 Hewlett Packard Enterprise HPE 公司开发的源代码检测工具 Fortify可以检测代码中的安全漏洞和缺陷共900多种 它通过对应用程序的源代码进行静态分析 自动检测安全性漏洞及缺陷 Fo
  • 使用ionic中ion-slide-box实现移动app轮播特效

    H5混合式移动开发框架ionic 是使用angularJS的语法 加上大名鼎鼎的移动应用开发框架cordova的核心 它的特点是跨平台 入门简单 可以减少开发周期 实质上 ionic就是用制作网页的技术来开发移动app 下面使用ionic中
  • c语言三角函数计算

    头文件 math h 计算 sin32 sin x 180 Pi 其他类似 因为要输入弧度才可以计算 直接sin 30 是不行的 sin x cos x tan x arcsin x arccos x arctan x arccot x 以
  • 基于Python的Anaconda3,导包报错 cannot import name 'Timestamp'

    问题 已经在cmd下使用 pip install ggplot 成功安装了ggplot包 在IDLE以及Jupyter Notebook下使用 from ggplot import 语句导入ggplot包时报错 ImportError ca
  • 外网不能访问postgresql解决办法

    安装PostgreSQL数据库之后 默认只能本地访问连接 如果想在其他主机上访问PostgreSQL数据库服务器 就需要进行相应的配置 1 修改postgresql conf文件 在安装目录下data postgresql confi文件中
  • C语言丨求两个正整数的最大公约数

    两个正整数的最大公约数 Greatest Common Divisor GCD 是能够整除这两个整数的最大整数 两个正整数的最大公约数的求法有多种解答 本文就三种方法做详细介绍 穷举法 欧几里得算法 辗转相除法 递归方法 我们从一道问题来引
  • Java配置Path环境变量

    安装JDK 首先下载JDK 下载后安装到指定目录 一般安装到 D 盘下 安装目录中不要出现 中文 字符和 空格 双击 JDK exe 安装JDK 双击后直接 下一步 更改安装目录 一般安装到 D 盘下 安装目录中不要出现 中文 字符和 空格
  • 安装MetaMask的谷歌浏览器扩展

    废话不多说直接上下载地址 因为各种下载不到 最后在github上找到了 完美 下载地址 下载完成之后进行解压 打开浏览器地址栏输入 chrome extensions 然后选择以下选项 选择到刚才解压后的目录 即完成安装
  • 计算机视觉基础6-目标检测

    目标检测 区域卷积神经网络R CNN 目标检测 检测图片中所有物体的类别标签 位置 最小外接矩形 bounding box 模块1 提取物体区域Region proposal 模块2 对区域分类识别 RCNN selective searc
  • String,StringBuilder和StringBuffer区别及使用场景

    面试中常常会遇到这样的问题 1 你了解String类吗 2 String StringBuilder和StringBuffer适合在什么样的场景下使用 1 String类 首先看一下String的源码 1 2 3 4 5 6 7 8 9 1
  • 刷题之移动零

    给定一个数组 nums 编写一个函数将所有 0 移动到数组的末尾 同时保持非零元素的相对顺序 示例 输入 0 1 0 3 12 输出 1 3 12 0 0 说明 必须在原数组上操作 不能拷贝额外的数组 尽量减少操作次数 来源 力扣 Leet
  • ESP8266-NodeMCU——从苏宁API获取实时天气

    前言 本篇介绍如何使用ESP8266 NodeMCU从苏宁API获取实时天气 苏宁API 点击跳转 其显示如下 其中我们要抓取的是红线部分的内容 并通过串口打印 当然 这部分也可以用来显示在OLED上 我之前就是这么玩 在正式开始前 需要了
  • 采用python解决实际问题_python使用ddt过程中遇到的问题及解决方案【推荐】

    前言 在使用DDT数据驱动 HTMLTestRunner输出测试报告时遇到过2个问题 1 生成的测试报告中 用例名称后有dict gt new empty dictionary 2 使用ddt生成的用例名称无法更改 1 用例名称后有dict
  • 区块链光谱

    虫洞社区签约作者介绍 叶露 王二 销售人员 克莱登技术有限公司 本文根据Taylor Pearson所著区块链光谱图 从密码学 分布式系统 政治学和经济学的角度对区块链做出的全方面分析 想象你是一位大学院长 学院正要新增一门关于区块链的课程
  • Visual ChatGPT原理解读——大模型论文阅读笔记四

    论文 https arxiv org abs 2303 04671 代码 https github com microsoft TaskMatrix 一 整体框架 如图所示 用户上传一张黄花的图像并输入一个复杂的语言指令 请根据该图像的预测
  • Springboot自带线程池

    一 ThreadPoolTaskExecuto 1 ThreadPoolTaskExecutor线程池 ThreadPoolTaskExecutor是Spring基于java本身的线程池ThreadPoolExecutor做的二次封装 主要
  • ConcurrentHashMap详解

    目录 ConcurrentHashMap介绍 ConcurrentHashMap底层数据结构 ConcurrentHashMap部分分析 ConcurrentHashMap与HashMap HashTable的区别 源码为jdk1 7 Co