6.ConcurrentHashMap jdk1.7
6.1 hash算法
就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
为了使得hash出来的值更加均衡,在concurrentHashMap里面使用了Wang/Jenkins hash算法再做了一次hash。
6.2 源码
6.2.1 构造方法
//DEFAULT_INITIAL_CAPACITY:默认的初始化容量,16
//DEFAULT_LOAD_FACTOR:默认的负载因子,0。75
//DEFAULT_CONCURRENCY_LEVEL:默认的并发等级,16
public ConcurrentHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
//点击this进去
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
//参数校验
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
//设置ssize的值,ssize是segments数组的大小,这里取的是大于等于concurrencyLevel的2^n的一个值。
int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
//设置segmentShift和segmentMask的值
//sshift就是上面描述中的n值,默认情况下concurrencyLevel等于16,sshift就等于4
//因此默认情况下segmentShift的值就是28,这个值会参与hash运算。
//segmentMask是hash运算的掩码,默认情况下等于16-1=15,类似于网络中的子网掩码,segmentMask的二进制最后几位都是1,最大值是末尾16个1(65535)。
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1;
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
//初始化segment,其中cap是segment里面的HashEntry数组的长度。它取的是大于等于c(Map容量/ssize)的2^N的一个值
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
//创建segments和segments[0](这里面只初始化了一个segments数组中的第0个元素)。
Segment<K,V> s0 =
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
UNSAFE.putOrderedObject(ss, SBASE, s0);
this.segments = ss;
}
小结:
--初始化currentHashMap时,初始化map的容量,负载因子,并发等级等信息
--默认会在map里面新建一个segment数组,并且会只初始化segments数组中的第0个元素。
6.2.2 get方法
get方法 get操作是先定位到segment,然后再到segment中去获取对应的value值
public V get(Object key) {
Segment<K,V> s;
HashEntry<K,V>[] tab;
int h = hash(key);
// 根据Segment的索引((h >>> segmentShift) & segmentMask)算出在Segment[]上的偏移量
// 默认情况下segmentShift为28,segmentMask为15(低位有1111),从而可以得到h的高四位的值。作为segment数组的索引。
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
//若segment存在则继续查找segment上面的table[]的索引位置
//根据table的索引((tab.length - 1) & h)算出在table[]上的偏移量,循环链表找出结果
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;
}
执行流程小结:
1。定位segment的位置,通过segment的索引(h >>> segmentShift) & segmentMask)算出Segment[]上的偏移量。
2。 根据table的索引((tab.length - 1) & h)算出table[]上的偏移量。
3。 循环HashEntry链表直到找到结果。
4。因为没有加锁如果在get的时候,同时有线程修改了hashEntry的值可能会出现获取不到真实的值。出现弱一致性的问题。
6.2.3 put方法
对于put()操作,前面的定位Segment的操作都是和put()相同的。找到Segment以后,然后对整个Segment加锁,然后再进行后续的操作
1。整体思路:
concurrentHashMap里面包含了一个segement数组,而每个segement数组元素中维护了一个HashEntry链表。
a 根据key算出的hash值,确定是属于哪个segement
b 如果对应的脚标segement存在,
c 如果对应的脚标未存在,新建一个,通过cas操作设置到segement数组中去。
2。put方法
public V put(K key, V value) {
Segment<K,V> s;
//1.校验
if (value == null)
throw new NullPointerException();
int hash = hash(key);
// 2. 定位Segment,并判断其是否存在
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);//如果Segment不存在,则新建。
return s.put(key, hash, value, false);// 如果Segment存在,提交给Segment去处理。调用Segment.put方法
}
//3 ensureSegment 分析
@SuppressWarnings("unchecked")
private Segment<K,V> ensureSegment(int k) {
final Segment<K,V>[] ss = this.segments;
long u = (k << SSHIFT) + SBASE; // segment数组的偏移量
Segment<K,V> seg;
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
Segment<K,V> proto = ss[0]; // 使用 segment 0 作为原型,可以省略一些参数的计算
int cap = proto.table.length;
float lf = proto.loadFactor;
int threshold = (int)(cap * lf);
HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) { // recheck
Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);//创建Segment。使用cas创建直到成功
while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) {
if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
break;
}
}
}
return seg;
}
//4 Segment.put()方法
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
//尝试加锁。如果尝试失败
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);
// 循环定位链表中的HashEntry位置,然后执行变更
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
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 {
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}
//小结
6.ConcurrentHashMap jdk1.7的更多相关文章
- ConcurrentHashMap(JDK1.8)为什么要放弃Segment
今天看到一篇博客:jdk1.8的HashMap和ConcurrentHashMap,我想起了前段时间面试的一个问题:ConcurrentHashMap(JDK1.8)为什么要使用synchronize ...
- Java泛型底层源码解析--ConcurrentHashMap(JDK1.7)
1. Concurrent相关历史 JDK5中添加了新的concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能.因为同步容器将所有对容器状态的访问都串行化了,这样保证了线程的安全 ...
- concurrenthashmap jdk1.8
参考:https://www.jianshu.com/p/c0642afe03e0 CAS的思想很简单:三个参数,一个当前内存值V.旧的预期值A.即将更新的值B,当且仅当预期值A和内存值V相同时,将内 ...
- 多线程-ConcurrentHashMap(JDK1.8)
前言 HashMap非线程安全的,HashTable是线程安全的,所有涉及到多线程操作的都加上了synchronized关键字来锁住整个table,这就意味着所有的线程都在竞争一把锁,在多线程的环境下 ...
- Java并发编程总结4——ConcurrentHashMap在jdk1.8中的改进(转)
一.简单回顾ConcurrentHashMap在jdk1.7中的设计 先简单看下ConcurrentHashMap类在jdk1.7中的设计,其基本结构如图所示: 每一个segment都是一个HashE ...
- Java并发编程总结4——ConcurrentHashMap在jdk1.8中的改进
一.简单回顾ConcurrentHashMap在jdk1.7中的设计 先简单看下ConcurrentHashMap类在jdk1.7中的设计,其基本结构如图所示: 每一个segment都是一个HashE ...
- ConcurrentHashMap原理分析(1.7与1.8)
前言 以前写过介绍HashMap的文章,文中提到过HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新 ...
- 并发容器和框架之ConcurrentHashMap
了解HashMap的人都知道HashMap是线程不安全的(多线程下的put方法达到一定大小,引发rehash,导致闭链,最终占满CPU),同时线程安全的HashTable效率又令人望而却步(每个方法都 ...
- 深入解析ConcurrentHashMap类
以前写过介绍HashMap的文章,文中提到过HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容 ...
随机推荐
- LibreOJ 6002 最小路径覆盖(最大流)
题解:最小路径覆盖=总点数减去最大匹配数,拆点,按照每条边前一个点连源点,后一个点连汇点跑最大流,即可跑出最大匹配数,然后减一减就可以了~ 代码如下: #include<queue> #i ...
- Python实现wc.exe
github传送门 项目相关要求 基本功能 -c file.c 返回文件file.c的字符数 (实现) -w file.c 返回文件file.c的词的数目(实现) -l file.c 返回文件file ...
- Django中使用后台网站模板
背景: 一直想自己开发一个网站,但是前端知识又不多,好在有模板可以使用,下载地址:https://download.csdn.net/download/wjgccsdn/10843808 开干: ...
- WP REST API: 设置和使用OAuth 1.0a Authentication(原文)
In the previous part of the series, we set up basic HTTP authentication on the server by installing ...
- EFCore扩展Select方法(根据实体定制查询语句)
EFCore扩展Select方法(根据实体定制查询语句) 通常用操作数据库的时候查询返回的字段是跟 我们的定义的实体是不一致的,所以往往针对UI或者接口层创建大量的Model, 而且需要手动对应字段 ...
- HBase基准性能测试报告
作者:范欣欣 本次测试主要评估线上HBase的整体性能,量化当前HBase的性能指标,对各种场景下HBase性能表现进行评估,为业务应用提供参考.本篇文章主要介绍此次测试的基本条件,HBase在各种测 ...
- 洛谷P4016 负载平衡问题(费用流)
传送门 嗯……完全不会……不过题解似乎讲的挺清楚…… 考虑一下,每一个仓库最终肯定都是平均数,所以数量大于平均数的可以往外运,小于平均数的要从别的地方运进来 考虑建一个超级源$S$和超级汇$T$,并把 ...
- Python3之时间模块time & datetime & calendar
一. 简介 python 提供很多方式处理日期与时间,转换日期格式是一个常见的功能. 时间元组:很多python函数用一个元组装起来的9组数字处理时间. python中时间日期格式化符号: %y 两位 ...
- Wiki凭什么持续得到开发人员和团队的喜爱
大家好,我是华为云DevCloud项目管理服务的产品经理恒少,作为布道师和产品经理,出差各地接触客户是常态,线下和华为云的客户交流.布道.技术沙龙. 但是线下交流,覆盖的用户总还是少数.我希望借助线上 ...
- SDUT OJ 数据结构实验之图论八:欧拉回路
数据结构实验之图论八:欧拉回路 Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Discuss Problem Descrip ...