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到新的扩容 ...
随机推荐
- [算法基础]Big O Notation时间复杂度计算方法
首先一点就是无视任何常量 从最简单的开始 statement; 这段时间复杂度为常数1,所以O(1). 然后 ; i < N; i++ ) statement; 这一段是线性的,则时间复杂度为N ...
- EF 配置实现建表与迁移
通过EF 作为操作数据库的工具有一段时间了,也做了几个相对不大的项目,慢慢的也对EF的使用摸索出来了一些规则,虽然说不是技术难点,但是,我说的是但是,能够提高我们开发效率的棉花糖有时我们还是必须要吃的 ...
- angular 样式属性绑定
<button (click)="onClick($event)">点我</button> <input type="> <ta ...
- 华硕X550VC安装ubuntu后wifi无法连接问题
在网上找了很多资料比如重新编译内核,想办法连上有线网络然后更新驱动,下载离线驱动安装包…… 等等方法 其中有些方法实际测试的时候失败了,文章是几年前的,可能缺少某些依赖.上个网都这么麻烦实在让人疲惫. ...
- 「POJ 1741」Tree
题面: Tree Give a tree with n vertices,each edge has a length(positive integer less than 1001). Define ...
- .db轻量级本地数据库
1.概述: db文件一般是数据库数据存放文件. db是datebase的缩写,是数据库文件. 我们可以简单的理解为db是本地轻量级数据库(用了缓存,储存少量本地数据,防止断电等突发意外的发生对我们的程 ...
- xcode工程配置绝对路径与相对路径
1.问题描述 一般我们在xcode里面配置包含工程目录下头文件的时候,都要关联着相对路径和绝对路径,如果只是自己用这个项目,用绝对路径的问题不大,但是如果你把工程发给别人,别人就要在改这个绝对路径,这 ...
- 移动端页面怎么适配ios页面
1.viewport 简单粗暴的方式:<meta name="viewport" content="width=320,maximum-scale=1.3,user ...
- 【bzoj1009】: [HNOI2008]GT考试 字符串-kmp-矩阵乘法-DP
[bzoj1009]: [HNOI2008]GT考试 先用kmp写个暴力 /* http://www.cnblogs.com/karl07/ */ #include <cstdlib> # ...
- 题解 P1720 【月落乌啼算钱】
题目链接 定义一个函数比较好求. #include<bits/stdc++.h>//万能头文件 using namespace std; double F(int x)//定义函数,为了保 ...