1.  先来了解ConcurrentHashMap中的几个成员,当然大多数与HashMap中的相似,我们只看独有的成员

/**
* The default concurrency level for this table, used when not
* otherwise specified in a constructor.
*/
static final int DEFAULT_CONCURRENCY_LEVEL = 16; //默认的并发级别
/**
* The maximum capacity, used if a higher value is implicitly
* specified by either of the constructors with arguments. MUST
* be a power of two <= 1<<30 to ensure that entries are indexable
* using ints.
*/
static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量
/**
* The minimum capacity for per-segment tables. Must be a power
* of two, at least two to avoid immediate resizing on next use
* after lazy construction.
*/
static final int MIN_SEGMENT_TABLE_CAPACITY = 2; //每个Segement中的桶的数量
/**
* The maximum number of segments to allow; used to bound
* constructor arguments. Must be power of two less than 1 << 24.
*/
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative //允许的最大的Segement的数量
/**
* Mask value for indexing into segments. The upper bits of a
* key's hash code are used to choose the segment.
*/
final int segmentMask; //掩码,用来定位segements数组的位置 /**
* Shift value for indexing within segments.
*/
final int segmentShift;       //偏移量,用来确认hash值的有效位
/**
* The segments, each of which is a specialized hash table.
*/
final Segment<K,V>[] segments; //相当于多个HashMap组成的数组

  

static final class Segment<K,V> extends ReentrantLock implements Serializable {  //内部类Segment,继承了ReentrantLock,有锁的功能
/**
* The maximum number of times to tryLock in a prescan before
* possibly blocking on acquire in preparation for a locked
* segment operation. On multiprocessors, using a bounded
* number of retries maintains cache acquired while locating
* nodes.
*/
static final int MAX_SCAN_RETRIES =
Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1; /**
* The per-segment table. Elements are accessed via
* entryAt/setEntryAt providing volatile semantics.
*/
transient volatile HashEntry<K,V>[] table; //每个Segement内部都有一个table数组,相当于每个Segement都是一个HashMap transient int count; //这些参数与HashMap中的参数功能相同 transient int modCount; transient int threshold; final float loadFactor; Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
this.loadFactor = lf;
this.threshold = threshold;
this.table = tab;
} final V put(K key, int hash, V value, boolean onlyIfAbsent) { //向Segement中添加一个元素 }

2. 构造函数

  

@SuppressWarnings("unchecked")
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;     //计算segement数组的大小,并且为2的倍数,默认情况下concurrentyLevel为16,那么ssize也为16
while (ssize < concurrencyLevel) {
++sshift; //ssize每次进行左移运算,因此sshift可以看做是ssize参数左移的位数
ssize <<= 1;
}
     
this.segmentShift = 32 - sshift; //segement偏移量
this.segmentMask = ssize - 1; //由于ssize为2的倍数,所以sengemnt为全1的,用来定位segement数组的下标
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
      
int c = initialCapacity / ssize; //计算每个Segement中桶的数量
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 = //初始化第一个segement
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize]; //新建segements数组,并将s0赋值
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}

3 . 我们来看put()方法

@SuppressWarnings("unchecked")
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)            //ConcurrentHashMap中value不能为空
throw new NullPointerException();
int hash = hash(key); //获取到key的hash值
int j = (hash >>> segmentShift) & segmentMask; //定位到某个segement位置
if ((s = (Segment<K,V>)UNSAFE.getObject //从上面的构造方法中我们知道,segments数组只有0位置的segment被初始化了,因此这里需要去检测计算出的位置的segment是否被初始化
                                      由于是并发容器,所以使用UNSAFE中的方法
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
return s.put(key, hash, value, false); //将元素插入到定位的Segement中
}
final V put(K key, int hash, V value, boolean onlyIfAbsent) {   //segement中的put()方法
HashEntry<K,V> node = tryLock() ? null : //获取锁,若获取不到锁,则县创建节点并返回
scanAndLockForPut(key, hash, value);
V oldValue;
try {                               
HashEntry<K,V>[] tab = table; //之后的算法就与HashMap中相似了
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);
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;
}

4. 来具体看一下SegementMask与SegmentShift这两个变量时怎么使用的?

  

     int sshift = 0;        
int ssize = 1;     //计算segement数组的大小,并且为2的倍数,默认情况下concurrentyLevel为16,那么ssize也为16
while (ssize < concurrencyLevel) {
++sshift; //ssize每次进行左移运算,因此sshift可以看做是ssize参数左移的位数
ssize <<= 1;
}
     
this.segmentShift = 32 - sshift; //segement偏移量
this.segmentMask = ssize - 1; //由于ssize为2的倍数,所以sengemnt为全1的,用来定位segement数组的下标

  上面是构造函数中计算这两个变量的代码。

  我们假设concurrencyLevel为默认值16,那么经过计算得到,ssize = 16,sshift = 4,segmentShift  = 28, segementMask = 15

  由于ssize为segements数组的大小,我们可以发现,当 n 与 segmentMask按位与时候正好可以得到<=15的数组,正是segements数组的下标。

  

 int j = (hash >>> segmentShift) & segmentMask;   //定位到某个segement位置

  而segementShift的作用在于缩小hash值的范围,我们并不需要使用hash值所有的位,通过上面的数据,当hash值右移28位后正好可以得到有效计算的位数(4位),因此上面构造函数中的sshift也

  可以表示计算segements数组时的有效位数。

ConcurrentHashMap(1.7)分析的更多相关文章

  1. Hashtable、ConcurrentHashMap源码分析

    Hashtable.ConcurrentHashMap源码分析 为什么把这两个数据结构对比分析呢,相信大家都明白.首先二者都是线程安全的,但是二者保证线程安全的方式却是不同的.废话不多说了,从源码的角 ...

  2. ConcurrentHashMap源码分析(一)

    本篇博客的目录: 前言 一:ConcurrentHashMap简介 二:ConcurrentHashMap的内部实现 三:总结 前言:HashMap很多人都熟悉吧,它是我们平时编程中高频率出现的一种集 ...

  3. ConcurrentHashMap 源码分析

    ConcurrentHashMap 源码分析 1. 前言    终于到这个类了,其实在前面很过很多次这个类,因为这个类代码量比较大,并且涉及到并发的问题,还有一点就是这个代码有些真的晦涩,不好懂.前前 ...

  4. 死磕 java集合之ConcurrentHashMap源码分析(三)

    本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...

  5. ConcurrentHashMap源码分析_JDK1.8版本

    在jdk1.8中主要做了2方面的改进 改进一:取消segments字段,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数 ...

  6. 并发-ConcurrentHashMap源码分析

    ConcurrentHashMap 参考: http://www.cnblogs.com/chengxiao/p/6842045.html https://my.oschina.net/hosee/b ...

  7. ConcurrentHashMap源码分析

    看过hashMap源码之后一直意犹未尽的感觉,挡不住我看其他的源码了.HashMap在单线程中非常好用,也不会出现什么问题,但是一到多线程就gg了,变的不灵了.我们有HashTable可以运用在多线程 ...

  8. Java并发系列[9]----ConcurrentHashMap源码分析

    我们知道哈希表是一种非常高效的数据结构,设计优良的哈希函数可以使其上的增删改查操作达到O(1)级别.Java为我们提供了一个现成的哈希结构,那就是HashMap类,在前面的文章中我曾经介绍过HashM ...

  9. ConcurrentHashMap源码分析(1.8)

    0.说明 1.ConcurrentHashMap跟HashMap,HashTable的对比 2.ConcurrentHashMap原理概览 3.ConcurrentHashMap几个重要概念 4.Co ...

  10. java基础系列之ConcurrentHashMap源码分析(基于jdk1.8)

    1.前提 在阅读这篇博客之前,希望你对HashMap已经是有所理解的,否则可以参考这篇博客: jdk1.8源码分析-hashMap:另外你对java的cas操作也是有一定了解的,因为在这个类中大量使用 ...

随机推荐

  1. bs4-爬取小说

    bs4 bs4有两种运行方式一种是处理本地资源,一种是处理网络资源 本地 from bs4 import BeautifulSoup if __name__ == '__main__': fr = o ...

  2. python_编程面试题

    使用递归方法对一个数组求最大值和最小值 """ 用递归算法求解一个数组的最大值和最小值 思路: 1.首先假设这个列表只有1个元素或两个元素 2.再考虑超过两个元素的情况, ...

  3. PTA 1139 1138 1137 1136

    PAT 1139 1138 1137 1136 一个月不写题,有点生疏..脑子跟不上手速,还可以啦,反正今天很开心. PAT 1139 First Contact 18/30 找个时间再修bug 23 ...

  4. cesium添加多个geojson文件并分别控制显示和隐藏

    /*获取geojson数据*/ function get_geojson(name,h,n){ let x=document.getElementById(n); if(x.className === ...

  5. pod install/update速度慢或失败的解决方案实践

    本文基于 https://www.cnblogs.com/dabaomo/p/9634727.html 声明 坚决拥护党的领导,本文章所用技术乃出于工作需要,敬请谅解. 正文 可以先过去快速浏览一遍再 ...

  6. 【Python成长之路】词云图制作

    [写在前面] 以前看到过一些大神制作的词云图 ,觉得效果很有意思.如果有朋友不了解词云图的效果,可以看下面的几张图(图片都是网上找到的): 网上找了找相关的软件,有些软件制作 还要付费.结果前几天在大 ...

  7. 【华为云网络技术分享】HTTP重定向HTTPS配置指南

    [摘要] 本文介绍使用华为云弹性负载均衡配置Http重定向到Https的方法. 1. HTTP.HTTPS 头部标识 ELB 对 HTTPS 进行代理,无论是 HTTP 还是 HTTPS 请求,到了  ...

  8. 使用node.js将xmind导出的excel转换为json树

    xmind文件如图所示, 最终生成的数据结构如图  2,选择导出为excel文件,导出的excel文件打开如图 3,安装node读取excel模块 cnpm i  node-xlsx --save 4 ...

  9. iOS开发-KVO的奥秘

    转自:http://www.jianshu.com/p/742b4b248da9 序言 在iOS开发中,苹果提供了许多机制给我们进行回调.KVO(key-value-observing)是一种十分有趣 ...

  10. Java修炼——FileInputStream和FileOutputStream

    文件字节流FileInputStream是读文件内容 有一下五个方法 1) abstract int read( ); 2) int read( byte b[ ] ); 3) int read( b ...