1、构造函数:

在构造函数中,主要就是根据ConcurrentHashMap的loadfactor、initialCapacity和concurrencyLevel来初始化这个ConcurrentHashMap。下面分别来介绍这几个参数的意义。

if (c++ > threshold) // ensure capacityrehash();

loadfactor是ConcurrentHashMap.Segment的负载因子。当Segment中元素的数目达到了threshold,就会调用rehash来扩容。Segment中的threshold的大小=Segment中表的大小*loadfactor。loadfactor的默认大小是0.75,代表当元素数目达到表的3/4的时候就会对这个Segment进行扩容。

if (concurrencyLevel > MAX_SEGMENTS)concurrencyLevel = MAX_SEGMENTS;// Find power-of-two sizes best matching argumentsint sshift = 0;int ssize = 1;while (ssize < concurrencyLevel) {++sshift;ssize <<= 1;}segmentShift = 32 - sshift;segmentMask = ssize - 1;this.segments = Segment.newArray(ssize);

concurrencyLevel是代表这个ConcurrentHashMap支持并发的等级,也就是有多少个Segment。concurrencyLevel的大小总是2的指数次方,并且不会超过1<<16。

if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;int c = initialCapacity / ssize;if (c * ssize < initialCapacity)++c;int cap = 1;while (cap < c)cap <<= 1;for (int i = 0; i < this.segments.length; ++i)this.segments[i] = new Segment<K,V>(cap, loadFactor);

initialCapacity是代表当前ConcurrentHashMap中的初始大小,并不是每个Segment的大小,initialCapacity不超过1<<30。注意代码中的cap,Segment的大小永远都是2的整数次幂大小,这样能使元素更加平均的分布到segments中。

2、Segment

Segment是一种分片锁的思想,在介绍ConcurrentHashMap的其他方法之前要先知道Segment的一些属性的作用。

transient volatile int count; //<span style="font-size:12px;">Segment中的元素数目</span>。transient int modCount; //Segment中修改元素的次数。transient int threshold; //扩容Segment的阈值。transient volatile HashEntry<K,V>[] table; //Segment储存元素的地方。final float loadFactor; //负载因子。

modCount调用的地方有put、remove、clear。replace则不会调用modCount,因为它没有Segment的表的物理结构。

final Segment<K,V> segmentFor(int hash) {return segments[(hash >>> segmentShift) & segmentMask];}

根据hash的高位来将该键值对放在不同的Segment。

3、put

put方法有两个版本,put和putIfAbsent。这两个方法的区别就是当key在ConcurrentHashMap已经存在的时候是否替换value。

V put(K key, int hash, V value, boolean onlyIfAbsent) {lock();try {int c = count;if (c++ > threshold) // ensure capacityrehash();HashEntry<K,V>[] tab = table;int index = hash & (tab.length - 1);HashEntry<K,V> first = tab[index];HashEntry<K,V> e = first;while (e != null && (e.hash != hash || !key.equals(e.key)))e = e.next;V oldValue;if (e != null) {oldValue = e.value;if (!onlyIfAbsent)e.value = value;}else {oldValue = null;++modCount;tab[index] = new HashEntry<K,V>(key, hash, first, value);count = c; // write-volatile}return oldValue;} finally {unlock();}}

在对表进行修改之前,会先加锁。然后判断是否需要对表扩容。接下来根据hash值计算地址取链表头。遍历链表,寻找Key是否已经在表里存在了,如果没有存在则直接从链表头插入,如果已经存在则要根据onlyIfAbsent来判断是否需要替换oldValue。返回oldValue,解锁。

4、rehash

rehash这个的访问权限是default的,所以只有子类和同包的类才可以调用它。在ConcurrentHashMap中发现只有Segment的put方法才调用了rehash,所以在rehash中并不需要加锁来防止出现并发问题。

void rehash() {HashEntry<K,V>[] oldTable = table;int oldCapacity = oldTable.length;if (oldCapacity >= MAXIMUM_CAPACITY)return;HashEntry<K,V>[] newTable = HashEntry.newArray(oldCapacity<<1);threshold = (int)(newTable.length * loadFactor);int sizeMask = newTable.length - 1;for (int i = 0; i < oldCapacity ; i++) {// We need to guarantee that any existing reads of old Map can//  proceed. So we cannot yet null out each bin.HashEntry<K,V> e = oldTable[i];if (e != null) {HashEntry<K,V> next = e.next;int idx = e.hash & sizeMask;//  Single node on listif (next == null)newTable[idx] = e;else {// Reuse trailing consecutive sequence at same slotHashEntry<K,V> lastRun = e;int lastIdx = idx;for (HashEntry<K,V> last = next;last != null;last = last.next) {int k = last.hash & sizeMask;if (k != lastIdx) {lastIdx = k;lastRun = last;}}newTable[lastIdx] = lastRun;// Clone all remaining nodesfor (HashEntry<K,V> p = e; p != lastRun; p = p.next) {int k = p.hash & sizeMask;HashEntry<K,V> n = newTable[k];newTable[k] = new HashEntry<K,V>(p.key, p.hash,n, p.value);}}}}table = newTable;}

获得Segment表的大小oldTable.length,然后乘2判断是否超过1<<30。如果大于直接返回。否则会使用oldTable.length<<1创建新的数组,并且计算新的threshold。接下来遍历oldTable把每个链表中的Entry重新插入到Segment中。因为Segment的大小是2的整数次幂,所以原来位置链表中的Entry只有两个选择,一个是插入在原位curIdx的链表中,一个是插入到oldTable.length+curIdx的链表中。源码中的处理方式,获得i位置上的链表头。如果非空,则判断next是否为空,如果next非空直接插入到对应位置做为链表头。如果next不为空,则需要先遍历一次链表,寻找最后一个idx不同的Entry。这里的优化措施就是第一次遍历链表记录下lastRun,lastRun后面的Entry的idx都和lastRun的idx相同,所以不需要多次插入,只需把lastRun及lastRun后面的节点一次插入到新链表的头后,然后开始重新遍历链表,将剩余的表中Entry插入到链表头中去。

5、get

get也会交给相应的Segment中去操作。但是get不会对表的结构产生改变,所以不用加锁。但是如果不加锁的话会产生一致性的问题,put到Segment中的Entry,如果并发读可能会访问不到相应的Entry。所以ConcurrentHashMap中的get是弱一致性的(具体方法大家可以参考http://ifeve.com/concurrenthashmap-weakly-consistent/)。

V get(Object key, int hash) {if (count != 0) { // read-volatileHashEntry<K,V> e = getFirst(hash);while (e != null) {if (e.hash == hash && key.equals(e.key)) {V v = e.value;if (v != null)return v;return readValueUnderLock(e); // recheck}e = e.next;}}return null;}

这里的count != 0的注释和put方法中的count=c的注释就解释了这两个操作是hb的,但是无法保证操作的原子性,所以没办法达到强一致性。

6、contain、containsValue

contain就是调用了containValue,下面上containsValue的源码。

public boolean containsValue(Object value) {if (value == null)throw new NullPointerException();// See explanation of modCount use abovefinal Segment<K,V>[] segments = this.segments;int[] mc = new int[segments.length];// Try a few times without lockingfor (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) {int sum = 0;int mcsum = 0;for (int i = 0; i < segments.length; ++i) {int c = segments[i].count;mcsum += mc[i] = segments[i].modCount;if (segments[i].containsValue(value))return true;}boolean cleanSweep = true;if (mcsum != 0) {for (int i = 0; i < segments.length; ++i) {int c = segments[i].count;if (mc[i] != segments[i].modCount) {cleanSweep = false;break;}}}if (cleanSweep)return false;}// Resort to locking all segmentsfor (int i = 0; i < segments.length; ++i)segments[i].lock();boolean found = false;try {for (int i = 0; i < segments.length; ++i) {if (segments[i].containsValue(value)) {found = true;break;}}} finally {for (int i = 0; i < segments.length; ++i)segments[i].unlock();}return found;}

判断ConcurrentHashMap中是否包含某个value就要在每个Segment去寻找是否包含这个value。首先先创建的mc数组就是记录每个Segment的modCount,然后去执行遍历查找操作,查找成功则会返回true。如果不成功则会再次遍历Segments的modCount去和mc中的每个值去比较,判断是否某个Segment发生了改变。如果发生了改变,cleanSweep被设置为false,则会重新进行。直到每个Segment在这期间都没有被修改过,然后会返回false。int c = segments[i].modCount;保证了可见性。如果每次都发现segments的Entry被修改过,那么重试次数达到RETRIES_BEFORE_LOCK的时候就会继续跳过无锁化的查找,对每个Segment进行加锁,然后再进行查找。

7、isEmpty

public boolean isEmpty() {final Segment<K,V>[] segments = this.segments;int[] mc = new int[segments.length];int mcsum = 0;for (int i = 0; i < segments.length; ++i) {if (segments[i].count != 0)return false;elsemcsum += mc[i] = segments[i].modCount;}if (mcsum != 0) {for (int i = 0; i < segments.length; ++i) {if (segments[i].count != 0 ||mc[i] != segments[i].modCount)return false;}}return true;}

在isEmpty中还是使用了记录segments的modCount的方式来进行统计,第一遍统计,如果有某个Segment的count不为0则直接返回false,如果统计segments的count都为0,那么需要第二次遍历segments,如果遍历的时候某个Segment的count不为0则直接返回false。如果某个Segment的count为0,并且modCount不为0,说明发生了ABA问题,那么就返回false,因为在isEmpty统计的过程中,segments并不为空。

转载请标明出处:http://blog.csdn.net/hahaha1232

JDK1.6的ConcurrentHashMap相关推荐

  1. 认真学习jdk1.8下ConcurrentHashMap的扩容机制

    关联博文: 认真学习jdk1.7下ConcurrentHashMap的实现原理 认真学习jdk1.8下ConcurrentHashMap的实现原理 认真学习jdk1.8下ConcurrentHashM ...

  2. JDK1.8 中 ConcurrentHashMap源码分析(一)容器初始化

    上一篇文章中说到如何使用IDEA搭建JDK1.8阅读学习环境,JDK1.8源码下载及获取.导入IDEA阅读.配置JDK源码.这篇文章将学习ConcurrentHashMap源码

  3. jdk1.8 HashMap ConcurrentHashMap

    JDK1.8逐字逐句带你理解ConcurrentHashMap https://blog.csdn.net/u012403290 JDK1.8理解HashMap https://blog.csdn.n ...

  4. JDK1.8 中 ConcurrentHashMap源码分析(二)元素添加是线程安全的

    上一篇说到了ConcurrentHashMap初始化

  5. Jdk1.8下ConcurrentHashMap常用方法的源码分析

    1.首先从下面这段代码开始分析 ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();map.pu ...

  6. ConcurrentHashMap源码分析(2)——JDK1.8的实现

    ConcurrentHashMap源码分析(1)--JDK1.7的实现 前言 在JDK1.7版本上,ConcurrentHashMap还是通过分段锁来实现的,Segment的数量制约着并发量.在JDK ...

  7. ConcurrentHashMap源码分析(1)——JDK1.7的实现

    ConcurrentHashMap源码分析 ConcurrentHashMap源码分析(2)--JDK1.8的实现 前言 ConcurrentHashMap是线程安全且高效的HashMap的实现,在并 ...

  8. currenthashmap扩容原理_高并发编程系列:深入探讨ConcurrentHashMap的实现原理(JDK1.7和JDK1.8)...

    HashMap.CurrentHashMap 的实现原理基本都是BAT面试必考内容,阿里P8架构师谈:深入探讨HashMap的底层结构.原理.扩容机制深入谈过hashmap的实现原理以及在JDK 1. ...

  9. Java多线程系列(八):ConcurrentHashMap的实现原理(JDK1.7和JDK1.8)

    HashMap.CurrentHashMap 的实现原理基本都是BAT面试必考内容,阿里P8架构师谈:深入探讨HashMap的底层结构.原理.扩容机制深入谈过hashmap的实现原理以及在JDK 1. ...

最新文章

  1. 香槟分校计算机科学排名,伊利诺伊大学厄巴纳-香槟分校计算机科学与工程世界排名2020年最新排名第42(ARWU世界排名)...
  2. Spring MVC框架处理Web请求的基本流程
  3. 在我生命里留下温暖记忆的一位老师
  4. 数据结构学习笔记:顺序表的删除操作及其演化题目总结
  5. 【转】如何在编程生涯中有一个好的开端
  6. 菜鸟修炼C语言小设计之——工资统计
  7. centos 下安装配置nfs服务器
  8. 西北大学计算机考试,西北大学计算机技术
  9. 通过避免下列 10 个常见 ASP.NET 缺陷使网站平稳运行
  10. hibernate中的一对多和多对多的映射关系
  11. 马化腾回应《腾讯没有梦想》:我的理想不是赚多少钱
  12. 动易 转 html5,动易系统所有标签解释5
  13. 康佳电视维修记 LED55M5580AF
  14. 遗传算法的基本原理和方法(转)
  15. 手把手教你制作智能桌宠(小可爱哦!)
  16. OKHTTP系列(九)---http请求头(header)作用
  17. 用英语提交软件测试bug,软件测试——关于提交bug随笔
  18. 武汉 华为 android,【武汉华为手机大全】武汉华为手机报价及图片大全-列表版-ZOL中关村在线...
  19. 【力扣-动态规划入门】【第 21 天】377. 组合总和 Ⅳ
  20. 苹果iTunes Store下架加密货币播客

热门文章

  1. 2014年国务院批准放假调休日期的具体安排通知
  2. c++dll导入导出宏定义,出现“无法定义dllimport 实体”和“不允许dllimport 静态数据成员的定义”的问题
  3. 推荐视频:浙大郑强教授演讲《中国强大的真正希望》
  4. 动作捕捉在各大领域的应用效果展示
  5. 24v转5v串多大电阻arduino
  6. 万向节死锁的个人理解
  7. 山西工商学院计算机信息工程学院,【第二课堂】山西工商学院“迎新赛”篮球赛会计学院VS计算机信息工程学院...
  8. 我用Python逆向登录世界上最大的游戏平台,steam加密手段有多高明【内附源码】
  9. MMA8452加速传感器的应用(简)
  10. DSP入门小白学习日记第三篇