阿里P7大佬!王者级讲解ConcurrentHashMap源码,码农:太透彻了
lipiwang 2024-11-06 19:41 6 浏览 0 评论
上一篇是分享的是《Scala - 类的定义》,这篇给大家分享《ConcurrentHashMap源码》。今天先更新一部分,一会儿要出门,谅解。
ConcurrentHashMap源码解读一
首先就先来说一下几个全局变量
private static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量2的30次方
private static final int DEFAULT_CAPACITY = 16; //默认容量 1<<4
private static final float LOAD_FACTOR = 0.75f; //负载因子
static final int TREEIFY_THRESHOLD = 8; //链表转为红黑树,大于8小于6先对链表数组进行翻倍扩容操作
static final int UNTREEIFY_THRESHOLD = 6; //树转列表
static final int MIN_TREEIFY_CAPACITY = 64; //链表真正转为红黑树
private static final int MIN_TRANSFER_STRIDE = 16;
private static int RESIZE_STAMP_BITS = 16;//stamp高位标识移动位数
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
static final int MOVED = -1; // forwarding nodes 的hash值,如果hash值等于-1代表线程协助扩容
static final int TREEBIN = -2; // roots of trees 的hash值,如果hash等于-2代表,当前桶是红黑树
static final int RESERVED = -3; // transient reservations 的hash值
// usable bits of normal node hash,在hash计算的时候运用到,与HashMap计算出来的hash值进行与操作
static final int HASH_BITS = 0x7fffffff;
static final int NCPU = Runtime.getRuntime().availableProcessors(); //可用处理器数量
然后是几个全局属性
transient volatile Node<K,V>[] table;//当前ConcurrentHashmap的Node数组,正在使用的数组
private transient volatile Node<K,V>[] nextTable;//ForwardNode所指向的下一个表,正在扩容的数组(还未使用)
private transient volatile long baseCount;//如果使用CAS计数成功,使用该值进行累加,计数用的
//扩容设置的参数,默认为0,当值=-1的时候,代表当前有线程正在进行扩容操作
//当值等于-n的时候,代表有n个线程一起扩容,其中n-1线程是协助扩容
//当在初始化的时候指定了大小,这会将这个大小保存在sizeCtl中,大小为数组的0.75
private transient volatile int sizeCtl;//标记状态以及数组阈值
private transient volatile int transferIndex;//数组扩容的时候用到
private transient volatile int cellsBusy;
//如果使用CAS计算失败,也就是说当前处于高并发的情况下,那么
//就会使用CounterCell[]数组进行计数,类似jdk1.7分段锁的形式,锁住一个segment
//最后size()方法统计出来的大小是baseCount和counterCells数组的总和
private transient volatile CounterCell[] counterCells;//计数数组。
首先是有参构造,这里如果是
ConcurrentHashMap chm = new ConcurrentHashMap(15);
那么其实容量不是15,而是32;
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
从这里可以看出是看tableSizeFor这个方法的,15+7+1=23;
private static final int tableSizeFor(int c) {
int n = c - 1;//23-1=22 0b10110
n |= n >>> 1;// 10110 | 01011 = 11111,下面都是11111也就是31
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;//31+1 = 32
}
所以这就是最后的容量,为32,也就是有参的参数的两倍最近的2的次方数。
接下来就将put方法
final V putVal(K key, V value, boolean onlyIfAbsent) {//onlyIfAbsent跟hashmap一样,就是判断是否要覆盖,默认为false,覆盖。
if (key == null || value == null) throw new NullPointerException();////这句话可以看出,ConcurrentHashMap中不允许存在空值,这个是跟HashMap的区别之一 //通过这个机制,我们可以通过get方法获取一个key,如果抛出异常,说明这个key不存在
int hash = spread(key.hashCode());//这个方法就相当于基于计算hash值。
int binCount = 0;//这个是记录这个桶的元素个数,目的是用它来判断是否需要转换红黑树,
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
//情况一:如果数组为空或者长度为0,进行初始化工作
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//情况二:如果获取的位置的节点为空,说明是首节点插入情况,也就是该桶位置没有元素,利用cas将元素添加。
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,//cas加自旋(和外侧的for构成自旋循环),保证元素添加安全
new Node<K,V>(hash, key, value, null)))//如果加成功了,那么就break,否则再经过for的死循环进行判断
break; // no lock when adding to empty bin
}
//情况三:如果hash计算得到的桶的位置元素的hash值为moved,也就是-1,证明正在扩容,那么就协助扩容。
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
//hash计算的桶位置元素不为空,且当前没有处于扩容操作,进行元素添加
//情况四:这个桶有元素,则执行插入操作,有两种可能,一是这个桶对应的链表没有相同的key,那么久再链表尾插入node节点,而是有相同的key,那么久替换其value。
else {
V oldVal = null;
synchronized (f) { //对当前桶进行加锁,保证线程安全,执行元素添加操作 //将桶位置的元素锁住,那么在该桶位中的链表或者红黑树进行添加元素的话,就是安全的,只有这个线程拿住了这个锁
if (tabAt(tab, i) == f) {//因为添加元素之后可能链表已经变成红黑树了,那么这个f就可能变化了。所以要再进行判断。
if (fh >= 0) {//说明是普通链表节点
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {//进行节点判断,如果都一样,那么覆盖旧值。
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {//尾插法,如果e的下一个不是null,那么循环会让pred变成e,直到最后节点,此时e的下一个为null的话
//那么也就是pred下一个为null,那么插入到pred下一个即可。
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) { //树节点,将元素添加到红黑树中
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)//链表长度大于/等于8,有可能将链表转成红黑树,因为在treeifyBin(tab, i);方法中还有一个判断数组长度是否小于64的判断,如果小于64,就不会
//树化。只是数组扩容。
treeifyBin(tab, i);
if (oldVal != null) //如果是重复键,直接将旧值返回
return oldVal;
break;
}
}
}
addCount(1L, binCount);//添加的是新元素,维护集合长度,并判断是否要进行扩容操作
return null;
}
总结:并发map,jdk1.8的情况下,底层跟hashmap一样,也是数组加链表加红黑树。
- 以上就是《ConcurrentHashMap源码解读一》的分享。
- 也欢迎大家交流探讨,该文章若有不正确的地方,希望大家多多包涵。
- 你们的支持就是我最大的动力,如果对大家有帮忙给个赞哦~~~
相关推荐
- linux实例之设置时区的方式有哪些
-
linux系统下的时间管理是一个复杂但精细的功能,而时区又是时间管理非常重要的一个辅助功能。时区解决了本地时间和UTC时间的差异,从而确保了linux系统下时间戳和时间的准确性和一致性。比如文件的时间...
- Linux set命令用法(linux cp命令的用法)
-
Linux中的set命令用于设置或显示系统环境变量。1.设置环境变量:-setVAR=value:设置环境变量VAR的值为value。-exportVAR:将已设置的环境变量VAR导出,使其...
- python环境怎么搭建?小白看完就会!简简单单
-
很多小伙伴安装了python不会搭建环境,看完这个你就会了Python可应用于多平台包括Linux和MacOSX。你可以通过终端窗口输入"python"命令来查看本地是否...
- Linux环境下如何设置多个交叉编译工具链?
-
常见的Linux操作系统都可以通过包管理器安装交叉编译工具链,比如Ubuntu环境下使用如下命令安装gcc交叉编译器:sudoapt-getinstallgcc-arm-linux-gnueab...
- JMeter环境变量配置技巧与注意事项
-
通过给JMeter配置环境变量,可以快捷的打开JMeter:打开终端。执行jmeter。配置环境变量的方法如下。Mac和Linux系统在~/.bashrc中加如下内容:export...
- C/C++|头文件、源文件分开写的源起及作用
-
1C/C++编译模式通常,在一个C++程序中,只包含两类文件——.cpp文件和.h文件。其中,.cpp文件被称作C++源文件,里面放的都是C++的源代码;而.h文件则被称...
- linux中内部变量,环境变量,用户变量的区别
-
unixshell的变量分类在Shell中有三种变量:内部变量,环境变量,用户变量。内部变量:系统提供,不用定义,不能修改环境变量:系统提供,不用定义,可以修改,可以利用export将用户变量转为环...
- 在Linux中输入一行命令后究竟发生了什么?
-
Linux,这个开源的操作系统巨人,以其强大的命令行界面而闻名。无论你是初学者还是经验丰富的系统管理员,理解在Linux终端输入一条命令并按下回车后发生的事情,都是掌握Linux核心的关键。从表面上看...
- Nodejs安装、配置与快速入门(node. js安装)
-
Nodejs是现代JavaScript语言产生革命性变化的一个主要框架,它使得JavaScript从一门浏览器语言成为可以在服务器端运行、开发各种各样应用的通用语言。在不同的平台下,Nodejs的安装...
- Ollama使用指南【超全版】(olaplex使用方法图解)
-
一、Ollama快速入门Ollama是一个用于在本地运行大型语言模型的工具,下面将介绍如何在不同操作系统上安装和使用Ollama。官网:https://ollama.comGithub:http...
- linux移植(linux移植lvgl)
-
1uboot移植l移植linux之前需要先移植一个bootlader代码,主要用于启动linux内核,lLinux系统包括u-boot、内核、根文件系统(rootfs)l引导程序的主要作用将...
- Linux日常小技巧参数优化(linux参数调优)
-
Linux系统参数优化可以让系统更加稳定、高效、安全,提高系统的性能和使用体验。下面列出一些常见的Linux系统参数优化示例,包括修改默认配置、网络等多方面。1.修改默认配置1.1修改默认编辑器默...
- Linux系统编程—条件变量(linux 条件变量开销)
-
条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等...
- 面试题-Linux系统优化进阶学习(linux系统的优化)
-
一.基础必备优化:1.关闭SElinux2.FirewalldCenetOS7Iptables(C6)安全组(阿里云)3.网络管理服务||NetworkManager|network...
- 嵌入式Linux开发教程:Linux Shell
-
本章重点介绍Linux的常用操作和命令。在介绍命令之前,先对Linux的Shell进行了简单介绍,然后按照大多数用户的使用习惯,对各种操作和相关命令进行了分类介绍。对相关命令的介绍都力求通俗易懂,都给...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- maven镜像 (69)
- undefined reference to (60)
- zip格式 (63)
- oracle over (62)
- date_format函数用法 (67)
- 在线代理服务器 (60)
- shell 字符串比较 (74)
- x509证书 (61)
- localhost (65)
- java.awt.headless (66)
- syn_sent (64)
- settings.xml (59)
- 弹出窗口 (56)
- applicationcontextaware (72)
- my.cnf (73)
- httpsession (62)
- pkcs7 (62)
- session cookie (63)
- java 生成uuid (58)
- could not initialize class (58)
- beanpropertyrowmapper (58)
- word空格下划线不显示 (73)
- jar文件 (60)
- jsp内置对象 (58)
- makefile编写规则 (58)