ConcurrentHashMap小记

ConcurrentHashMap和HashTable比,是一种更高效的线程安全Map。
HashTable通过synchronized保证线程安全,而且对所有操作均进行了加锁。当线程竞争激烈的情况下,由于要竞争同一把锁,效率就非常低下了。
ConcurrentHashMap通过引入多个锁,缓解了上述问题。通过将底层数据分成多个Segment,每个Segment分配一个锁,当一个Segment的锁被占用的时候,并不影响其他Segment的正常读写。

ConcurrentHashMap结构

我们知道,HashMap是通过数组和单链表实现的,参见:HashMap底层实现原理 ConcurrentHashMap和其很类似,只是多了一层Segment数组,每个数组里面存的数据结构和HashMap一致,为数组和单链表结构。
这里写图片描述

初始化

初始化segments数组

ConcurrentHashMap中Segment的长度,用ssize来表示,如下所示

int sshift = 0;
int ssize = 1;
while (ssize < concurrencyLevel) {
    ++sshift;
    ssize <<= 1;
}

如上所示,sshift等于ssize从1向左移动的次数。默认情况下,concurrencyLevel为16,所以sshift等于4。

初始化每个segment

一个Segment元素下的HashEntry的初始化也是按照位于运算来计算,用cap来表示,如下所示

int cap = 1;
while (cap < c)
    cap <<= 1;

如上所示,HashEntry大小的计算也是2的N次方(cap <<=1)。

get操作

ConcurrentHashMap的get操作跟HashMap类似,只是ConcurrentHashMap第一次需要经过一次hash定位到Segment的位置,然后再hash定位到指定的HashEntry,遍历该HashEntry下的链表进行对比,成功就返回,不成功就返回null。
(hash >>> segmentShift) & segmentMask 定位segment索引
hash & (tab.length -1) 定位HashEntry
和HashTable对get操作也上锁不同,ConcurrentHashMap通过volatile保证了共享变量的可见性。

put操作

对于ConcurrentHashMap的数据插入,这里要进行两次Hash去定位数据的存储位置
static class Segment<K,V> extends ReentrantLock implements Serializable {
从上Segment的继承体系可以看出,Segment实现了ReentrantLock,也就带有锁的功能,当执行put操作时,会进行第一次key的hash来定位Segment的位置,如果该Segment还没有初始化,即通过CAS操作进行赋值,然后进行第二次hash操作,找到相应的HashEntry的位置,这里会利用继承过来的锁的特性,在将数据插入指定的HashEntry位置时(链表的尾端),会通过继承ReentrantLock的tryLock()方法尝试去获取锁,如果获取成功就直接插入相应的位置,如果已经有线程获取该Segment的锁,那当前线程会以自旋的方式去继续的调用tryLock()方法去获取锁,超过指定次数就挂起,等待唤醒。

在插入元素前,会判断segment是否扩容的判断,它比HashMap要恰当,因为HashMap是插入后再判断,可能扩容后没有新元素插入,造成了无效扩容。
ConcurrentHashMap扩容时,只会对特定segment里的HashEntry数组扩容。

size操作

计算ConcurrentHashMap的元素大小是一个有趣的问题,因为他是并发操作的,就是在你计算size的时候,他还在并发的插入数据,可能会导致你计算出来的size和你实际的size有相差(在你return size的时候,插入了多个数据),要解决这个问题,JDK1.7版本用两种方案。

try {
for (;;) {
if (retries++ == RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j) ensureSegment(j).lock(); // force creation
}
sum = 0L;
size = 0;
overflow = false;
for (int j = 0; j < segments.length; ++j) {
Segment

JDK1.8的实现

JDK1.8的实现已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本。
这里写图片描述

引用:http://www.importnew.com/26049.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值