ConcurrentHashMap源码解析(1)
此文已由作者赵计刚授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
注:在看这篇文章之前,如果对HashMap的层不清楚的话,建议先去看看HashMap源码解析。
http://www.cnblogs.com/java-zhao/p/5106189.html
1、对于ConcurrentHashMap需要掌握以下几点
Map的创建:ConcurrentHashMap()
往Map中添加键值对:即put(Object key, Object value)方法
获取Map中的单个对象:即get(Object key)方法
删除Map中的对象:即remove(Object key)方法
判断对象是否存在于Map中:containsKey(Object key)
遍历Map中的对象:即keySet().iterator(),在实际中更常用的是增强型的for循环去做遍历
2、ConcurrentHashMap的创建
注:在往下看之前,心里先有这样一个映像:ConcurrentHashMap的数据结构:一个指定个数的Segment数组,数组中的每一个元素Segment相当于一个HashTable。
2.1、使用方法:
Map<String, Object> map = new ConcurrentHashMap<String, Object>();
2.2、源代码:
ConcurrentHashMap相关属性:
/**
* 用于分段
*/
// 根据这个数来计算segment的个数,segment的个数是仅小于这个数且是2的几次方的一个数(ssize)
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// 最大的分段(segment)数(2的16次方)
static final int MAX_SEGMENTS = 1 << 16;
/**
* 用于HashEntry
*/
// 默认的用于计算Segment数组中的每一个segment的HashEntry[]的容量,但是并不是每一个segment的HashEntry[]的容量
static final int DEFAULT_INITIAL_CAPACITY = 16;
// 默认的加载因子(用于resize)
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 用于计算Segment数组中的每一个segment的HashEntry[]的最大容量(2的30次方)
static final int MAXIMUM_CAPACITY = 1 << 30; /**
* segments数组
* 每一个segment元素都看做是一个HashTable
*/
final Segment<K, V>[] segments;
/**
* 用于扩容
*/
final int segmentMask;// 用于根据给定的key的hash值定位到一个Segment
final int segmentShift;// 用于根据给定的key的hash值定位到一个Segment
Segment类(ConcurrentHashMap的内部类)
/**
* 一个特殊的HashTable
*/
static final class Segment<K, V> extends ReentrantLock implements
Serializable { private static final long serialVersionUID = 2249069246763182397L; transient volatile int count;// 该Segment中的包含的所有HashEntry中的key-value的个数
transient int modCount;// 并发标记 /*
* 元素个数超出了这个值就扩容 threshold==(int)(capacity * loadFactor)
* 值得注意的是,只是当前的Segment扩容,所以这是Segment自己的一个变量,而不是ConcurrentHashMap的
*/
transient int threshold;
transient volatile HashEntry<K, V>[] table;// 链表数组
final float loadFactor; /**
* 这里要注意一个很不好的编程习惯,就是小写l,容易与数字1混淆,所以最好不要用小写l,可以改为大写L
*/
Segment(int initialCapacity, float lf) {
loadFactor = lf;//每个Segment的加载因子
setTable(HashEntry.<K, V> newArray(initialCapacity));
} /**
* 创建一个Segment数组,容量为i
*/
@SuppressWarnings("unchecked")
static final <K, V> Segment<K, V>[] newArray(int i) {
return new Segment[i];
} /**
* Sets table to new HashEntry array. Call only while holding lock or in
* constructor.
*/
void setTable(HashEntry<K, V>[] newTable) {
threshold = (int) (newTable.length * loadFactor);// 设置扩容值
table = newTable;// 设置链表数组
}
说明:只列出了Segement的全部属性和创建ConcurrentHashMap时所用到的方法。
HashEntry类(ConcurrentHashMap的内部类)
/**
* Segment中的HashEntry节点 类比HashMap中的Entry节点
*/
static final class HashEntry<K, V> {
final K key;// 键
final int hash;//hash值
volatile V value;// 实现线程可见性
final HashEntry<K, V> next;// 下一个HashEntry HashEntry(K key, int hash, HashEntry<K, V> next, V value) {
this.key = key;
this.hash = hash;
this.next = next;
this.value = value;
} /*
* 创建HashEntry数组,容量为传入的i
*/
@SuppressWarnings("unchecked")
static final <K, V> HashEntry<K, V>[] newArray(int i) {
return new HashEntry[i];
}
}
ConcurrentHashMap(int initialCapacity,float loadFactor,int concurrencyLevel)
1 /**
2 * 创建ConcurrentHashMap
3 * @param initialCapacity 用于计算Segment数组中的每一个segment的HashEntry[]的容量, 但是并不是每一个segment的HashEntry[]的容量
4 * @param loadFactor
5 * @param concurrencyLevel 用于计算Segment数组的大小(可以传入不是2的几次方的数,但是根据下边的计算,最终segment数组的大小ssize将是2的几次方的数)
6 *
7 * 步骤:
8 * 这里以默认的无参构造器参数为例,initialCapacity==16,loadFactor==0.75f,concurrencyLevel==16
9 * 1)检查各参数是否符合要求
10 * 2)根据concurrencyLevel(16),计算Segment[]的容量ssize(16)与扩容移位条件sshift(4)
11 * 3)根据sshift与ssize计算将来用于定位到相应Segment的参数segmentShift与segmentMask
12 * 4)根据ssize创建Segment[]数组,容量为ssize(16)
13 * 5)根据initialCapacity(16)与ssize计算用于计算HashEntry[]容量的参数c(1)
14 * 6)根据c计算HashEntry[]的容量cap(1)
15 * 7)根据cap与loadFactor(0.75)为每一个Segment[i]都实例化一个Segment
16 * 8)每一个Segment的实例化都做下面这些事儿:
17 * 8.1)为当前的Segment初始化其loadFactor为传入的loadFactor(0.75)
18 * 8.2)创建一个HashEntry[],容量为传入的cap(1)
19 * 8.3)根据创建出来的HashEntry的容量(1)和初始化的loadFactor(0.75),计算扩容因子threshold(0)
20 * 8.4)初始化Segment的table为刚刚创建出来的HashEntry
21 */
22 public ConcurrentHashMap(int initialCapacity,float loadFactor,int concurrencyLevel) {
23 // 检查参数情况
24 if (loadFactor <= 0f || initialCapacity < 0 || concurrencyLevel <= 0)
25 throw new IllegalArgumentException();
26
27 if (concurrencyLevel > MAX_SEGMENTS)
28 concurrencyLevel = MAX_SEGMENTS;
29
30 /**
31 * 找一个能够正好小于concurrencyLevel的数(这个数必须是2的几次方的数)
32 * eg.concurrencyLevel==16==>sshift==4,ssize==16
33 * 当然,如果concurrencyLevel==15也是上边这个结果
34 */
35 int sshift = 0;
36 int ssize = 1;// segment数组的长度
37 while (ssize < concurrencyLevel) {
38 ++sshift;
39 ssize <<= 1;// ssize=ssize*2
40 }
41
42 segmentShift = 32 - sshift;// eg.segmentShift==32-4=28 用于根据给定的key的hash值定位到一个Segment
43 segmentMask = ssize - 1;// eg.segmentMask==16-1==15 用于根据给定的key的hash值定位到一个Segment
44 this.segments = Segment.newArray(ssize);// 构造出了Segment[ssize]数组 eg.Segment[16]
45
46 /*
47 * 下面将为segment数组中添加Segment元素
48 */
49 if (initialCapacity > MAXIMUM_CAPACITY)
50 initialCapacity = MAXIMUM_CAPACITY;
51 int c = initialCapacity / ssize;// eg.initialCapacity==16,c==16/16==1
52 if (c * ssize < initialCapacity)// eg.initialCapacity==17,c==17/16=1,这时1*16<17,所以c=c+1==2
53 ++c;// 为了少执行这一句,最好将initialCapacity设置为2的几次方
54 int cap = 1;// 每一个Segment中的HashEntry[]的初始化容量
55 while (cap < c)
56 cap <<= 1;// 创建容量
57
58 for (int i = 0; i < this.segments.length; ++i)
59 // 这一块this.segments.length就是ssize,为了不去计算这个值,可以直接改成i<ssize
60 this.segments[i] = new Segment<K, V>(cap, loadFactor);
61 }
注意:这个方法里边我在头部所写的注释非常重要,在这块注释写明了:
每一个参数的作用
整个ConcurrentHashMap的一个创建步骤(以默认的参数值为例)
免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 FUI-我离钢铁侠还差几步?
【推荐】 网易云数据库架构设计实践
【推荐】 云计算交互设计师的正确出装姿势
ConcurrentHashMap源码解析(1)的更多相关文章
- Java之ConcurrentHashMap源码解析
ConcurrentHashMap源码解析 目录 ConcurrentHashMap源码解析 jdk8之前的实现原理 jdk8的实现原理 变量解释 初始化 初始化table put操作 hash算法 ...
- 第二章 ConcurrentHashMap源码解析
注:在看这篇文章之前,如果对HashMap的层不清楚的话,建议先去看看HashMap源码解析. http://www.cnblogs.com/java-zhao/p/5106189.html 1.对于 ...
- ConcurrentHashMap源码解析,多线程扩容
前面一篇已经介绍过了 HashMap 的源码: HashMap源码解析.jdk7和8之后的区别.相关问题分析 HashMap并不是线程安全的,他就一个普通的容器,没有做相关的同步处理,因此线程不安全主 ...
- Java并发包源码学习系列:JDK1.8的ConcurrentHashMap源码解析
目录 为什么要使用ConcurrentHashMap? ConcurrentHashMap的结构特点 Java8之前 Java8之后 基本常量 重要成员变量 构造方法 tableSizeFor put ...
- 数据结构算法 - ConcurrentHashMap 源码解析
五个线程同时往 HashMap 中 put 数据会发生什么? ConcurrentHashMap 是怎么保证线程安全的? 在分析 HashMap 源码时还遗留这两个问题,这次我们站在 Java 多线程 ...
- 并发编程(十六)——java7 深入并发包 ConcurrentHashMap 源码解析
以前写过介绍HashMap的文章,文中提到过HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容 ...
- 深入并发包 ConcurrentHashMap 源码解析
以前写过介绍HashMap的文章,文中提到过HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容 ...
- ConcurrentHashMap源码解析 JDK8
一.简介 上篇文章详细介绍了HashMap的源码及原理,本文趁热打铁继续分析ConcurrentHashMap的原理. 首先在看本文之前,希望对HashMap有一个详细的了解.不然看直接看Concur ...
- ConcurrentHashMap源码解析-Java7
目录 一.ConcurrentHashMap的模型图 二.源码分析-类定义 2.1 极简ConcurrentHashMap定义 2.2 Segment内部类 2.3 HashEntry内部类 2.4 ...
随机推荐
- 校园网ipv6连接问题
没有ipv6的信号:只需要进入网络适配器里面先禁用再启用即可.
- 【UI测试】--规范性
- js中将斜杠\替换的方法
js中将/替换的方法replace(/\//g, '-') 中间涉及到js的一些转义问题,试了几个方法,发现这个可以,就记下来.
- Mysql 常用增删改查命令集合教程
创建:create 插入:insert 更新:update 查询:select 删除:delete 修改:alter 销毁:drop 创建一个数据库: create databas ...
- ajax在jQuery中的应用 (1)加载异步数据
- Python常见错误:IndexError: list index out of range
用python写脚本查询字典时,在遍历字典时循环到某一项时老是报错 出现这种错误有两种情况: 第1种可能情况 list[index]index超出范围 第2种可能情况 list是空值就会出现 In ...
- 2019.01.22 zoj3583 Simple Path(并查集+枚举)
传送门 题意简述:给出一张图问不在从sss到ttt所有简单路径上的点数. 思路: 枚举删去每个点然后把整张图用并查集处理一下,同时不跟sss和ttt在同一个连通块的点就是满足要求的点(被删去的不算). ...
- react属性绑定
1.属性值绑定state里的数据,不用引号 class App extends Component { constructor(props){ super(props); this.state = { ...
- vue中$route 和$router的区别
在vue中会出现一种情况 const url=this.$route.query.returnURL; this.$router.push(url); $router和$route的区别傻傻的分 ...
- i2c总线,核心,驱动详解
Linux I2C驱动分析(一)----I2C架构和总线驱动 一.I2C总线原理 I2C是一种常用的串行总线,由串行数据线SDA 和串线时钟线SCL组成.I2C是一种多主机控制总线,它和USB总线不同 ...