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到新的扩容 ...
随机推荐
- 二度xml<一>
又一次学习Xml,之前差不多都忘了,为了下半年的面试,为了工作重头来过....... 其实我觉得直接上代码来的更实际点,理论的东西,我们随便找点书看看就行. 下面的代码是为了打印出一个xml文件 xm ...
- 编写高质量代码改善C#程序的157个建议——建议55:利用定制特性减少可序列化的字段
建议55:利用定制特性减少可序列化的字段 特性(attribute)可以声明式地为代码中的目标元素添加注释.运行时可以通过查询这些托管块中的元数据信息,达到改变目标元素运行时行为的目的.System. ...
- java中公用类型Car必须在它自己的文件中定义
熟悉java的过程中发现了一些小问题,定义的类Car老是提示必须在它自己的文件中定义.自己想了想试试把Car继承的类Vehicle中的public换到Car类中,结果发现输出问题很大.它只显示了一个输 ...
- 以太坊系列之三: 以太坊的crypto模块--以太坊源码学习
以太坊的crypto模块 该模块分为两个部分一个是实现sha3,一个是实现secp256k1(这也是比特币中使用的签名算法). 需要说明的是secp256k1有两种实现方式,一种是依赖libsecp2 ...
- (c++11)随机数------c++程序设计原理与实践(进阶篇)
随机数既是一个实用工具,也是一个数学问题,它高度复杂,这与它在现实世界中的重要性是相匹配的.在此我们只讨论随机数哦最基本的内容,这些内容可用于简单的测试和仿真.在<random>中,标准库 ...
- kali linux之手动漏洞挖掘三(sql注入)
服务器端程序将用户输入作为参数作为查询条件,直接拼写sql语句,并将结果返回给客户端浏览器 如判断登录 select * from users where user='uname' and passw ...
- 洛谷P4011 孤岛营救问题(状压+BFS)
传送门 和网络流有半毛钱关系么…… 可以发现$n,m,p$都特别小,那么考虑状压,每一个状态表示位置以及钥匙的拥有情况,然后每次因为只能走一步,所以可以用bfs求出最优解 然后是某大佬说的注意点:每个 ...
- centos7 docker 安装 zookeeper 3.4.13 集群
假设三台主机的ip分别为: 主机一:192.168.0.168 主机二:192.168.0.169 主机三:192.168.0.170 三台主机的安装步骤相似,以主机一为例: 1. 查找zookeep ...
- Python获取网页Html文本
Python爬虫基础 1.获取网页文本 通过urllib2包,根据url获取网页的html文本内容并返回 #coding:utf-8 import requests, json, time, re, ...
- [jvm]java内存模型
一.java内存模型 Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一 ...