JDK1.7 ConcurrentHashMap 源码浅析
概述
ConcurrentHashMap是HashMap的线程安全版本,使用了分段加锁的方案,在高并发时有比较好的性能。
本文分析JDK1.7中ConcurrentHashMap的实现。
正文
ConcurrentHashMap概述
HashMap不是线程安全的,要实现线程安全除非加锁,但这样性能很低。ConcurrentHashMap把整个HashMap数组分成了若干个Segment,每个Segment里有一个数组。添加一个Key时,需要先根据hash值计算出其所在Segment,然后再根据hash值计算出在该Segment中的位置。Segment继承自ReentrantLock,每个Segment就是一个锁。在多线程的情况下,就减少了锁竞争,提升了性能。
ConcurrentHashMap存储结构如下图所示:
下面我们来分析源码,看数据是怎么存储的。
构造函数
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;
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;
//concurrencyLevel为并发级别,这一步就是计算出大于concurrencyLevel的最小的2的N次方
//为什么不用HashMap中的Integer.highestOneBit((number - 1) << 1)来计算这个值
//我的理解是concurrencyLevel一般都比较小(默认为16),采用这种计算方法效率更高。
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
//后面根据hash计算segment位置时需要用到
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1;
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//计算每一个segment中table的length
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
// create segments and 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); // ordered write of segments[0]
this.segments = ss;
}
和HashMap最大的不同就是多了Segment的初始化。
Segment的Size也初始化为2的N次方,这为后面的Map整体resize以及确定一个hash值所在Segment都提供简便方法。
每个Segment中的table同HashMap中table一样,接着来看PUT时怎么计算Segment的位置。
PUT
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
int hash = hash(key);
//取得Key的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);
return s.put(key, hash, value, false);
}
segmentShift:在构造函数中计算出来的,假设concurrencyLevel为16,segmentShift=28(32-4)
segmentMask:15(16-1)
可见求Key所在Segment的算法和HashMap中求Key所在table中的位置一样,都是 hash & (length-1)。
所以这里Segment的length也必须是2的N次方。
hash >>> segmentShift是为了使用hash的高位进行与运算。
s.put方法,就是把Key放到Segment中table的响应位置,它的算法和HashMap中类似,只是加入了锁。
线程安全
HashMap - 非线程安全
Put一个Key时有下面这段代码:
void createEntry(int hash, K key, V value, int bucketIndex) {
//1.取得链表
Entry<K,V> e = table[bucketIndex];
//2.将新Key设置为链表的第一个
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
假设有两个线程A、B,同时进行第1步,它们获取到的e是同一个,如:x,y,z
然后线程A运行到第2步,为e添加了一个新元素a,并赋值给table[bucketIndex],此时table[bucketIndex]为:a,x,y,z
而后线程B运行到第2步,为e添加了一个新元素b,并赋值给table[bucketIndex],此时table[bucketIndex]为:b,x,y,z
所以这种情况下就会有问题,这只是其中的一个例子,所以HashMap是非线程安全的。
ConcurrentHashMap - 线程安全
Put一个Key到Table时,使用如下代码:
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
......
} finally {
unlock();
}
return oldValue;
}
可以看到put时,加入了Lock,这就保证了线程的安全性。
查看ConcurrentHashMap源代码可以发现,ConcurrentHashMap的remove、replace等有可能引起线程安全问题的地方都加了Lock。
ConcurrentHashMap的Get方法并不是完全线程安全,因为Get时没有加锁,但JDK用了很多volatile类型变量来保证在大多数情况下的线程安全。
具体怎么线程不安全,参考:深入剖析ConcurrentHashMap
总结:
ConcurrentHashMap在绝大多数情况下是线程安全的,在多线程情况下请使用ConcurrentHashMap。
参考:
1. Java 8系列之重新认识HashMap http://tech.meituan.com/java-hashmap.html
2. 深入剖析ConcurrentHashMap http://ifeve.com/java-concurrent-hashmap-2
3. jdk8之ConcurrentHashMap源码解析 http://jahu.iteye.com/blog/2331191
JDK1.7 ConcurrentHashMap 源码浅析的更多相关文章
- java并发:jdk1.8中ConcurrentHashMap源码浅析
ConcurrentHashMap是线程安全的.可以在多线程中对ConcurrentHashMap进行操作. 在jdk1.7中,使用的是锁分段技术Segment.数据结构是数组+链表. 对比jdk1. ...
- JDK1.8 ConcurrentHashMap源码阅读
1. 带着问题去阅读 为什么说ConcurrentHashMap是线程安全的?或者说 ConcurrentHashMap是如何防止并发的? 2. 字段和常量 首先,来看一下ConcurrentHa ...
- jdk1.7 ArrayList源码浅析
参考:http://www.cnblogs.com/xrq730/p/4989451.html(借鉴的有点多,哈哈) 首先介绍ArrayList的特性: 1.允许元素为空.允许重复元素 2.有序,即插 ...
- spring源码浅析——IOC
=========================================== 原文链接: spring源码浅析--IOC 转载请注明出处! ======================= ...
- Hashtable、ConcurrentHashMap源码分析
Hashtable.ConcurrentHashMap源码分析 为什么把这两个数据结构对比分析呢,相信大家都明白.首先二者都是线程安全的,但是二者保证线程安全的方式却是不同的.废话不多说了,从源码的角 ...
- ConcurrentHashMap源码阅读
1. 前言 HashMap是非线程安全的,在多线程访问时没有同步机制,并发场景下put操作可能导致同一数组下的链表形成闭环,get时候出现死循环,导致CPU利用率接近100%. HashTable是线 ...
- Android源码浅析(二)——Ubuntu Root,Git,VMware Tools,安装输入法,主题美化,Dock,安装JDK和配置环境
Android源码浅析(二)--Ubuntu Root,Git,VMware Tools,安装输入法,主题美化,Dock,安装JDK和配置环境 接着上篇,上片主要是介绍了一些安装工具的小知识点Andr ...
- JDK12 concurrenthashmap源码阅读
本文部分照片和代码分析来自文末参考资料 java8中的concurrenthashmap的方法逻辑和注解有些问题,建议看最新的JDK版本 建议阅读 concu ...
- CountDownLatch源码浅析
Cmd Markdown链接 CountDownLatch源码浅析 参考好文: JDK1.8源码分析之CountDownLatch(五) Java并发之CountDownLatch源码分析 Count ...
随机推荐
- PostConstruct
Spring AOP注解通过@Autowired,@Resource,@Qualifier,@PostConstruct,@PreDestroy注入属性的配置文件详解 1.6. @PostConstr ...
- codevs1225 八数码难题
题目描述 Description Yours和zero在研究A*启发式算法.拿到一道经典的A*问题,但是他们不会做,请你帮他们.问题描述 在 3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数 ...
- SQLServer错误:过程 sp_addextendedproperty,第 xxx 行对象无效。'dbo.xxx.xxx' 不允许有扩展属性,或对象不存在。
上传数据库到虚拟主机,在执行SQL脚本的时候出现以下的错误: 消息 15135,级别 16,状态 8,过程 sp_addextendedproperty,第 37 行 对象无效.'dbo.Messag ...
- BC68(HD5606) 并查集+求集合元素
tree Accepts: 143 Submissions: 807 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65 ...
- MySQL的表分区详解
这篇文章主要介绍了MySQL的表分区,例如什么是表分区.为什么要对表进行分区.表分区的4种类型详解等,需要的朋友可以参考下 一.什么是表分区通俗地讲表分区是将一大表,根据条件分割成若干个小表.mysq ...
- static 类也可以有static构造函数
public static class A { static A() { } } static构造函数不能是public,也不可能被主动调用,所以public没有意义
- 安装coreseek找不到mysql
1.安装 coreseek-3.2.14 遇到问题:“ERROR: cannot find MySQL include files,随即在网上搜索各种答案说是要找到mysql.h的正确路径加入./co ...
- tp 多语言支持
tp支持多语言 通过get来改变语言的 http://localhost/tp/index.php/Admin/User/add/hl/zh-cn http://localhost/tp/index. ...
- JavaScript中call、apply、bind、slice的使用
1.参考资料 http://www.cnblogs.com/coco1s/p/4833199.html 2.归结如下 apply . call .bind 三者都是用来改变函数的this对象的指向 ...
- 一步一步教你如何在linux下配置apache+tomcat(转)
一步一步教你如何在linux下配置apache+tomcat 一.安装前准备. 1. 所有组件都安装到/usr/local/e789目录下 2. 解压缩命令:tar —vxzf 文件名(. ...