最近又看了一些关于Java8下ConcurrentHashMap的分析,自己也尝试通过源码来进行一些理解,这里总结性的记录下一些收获,更详细的资料,网上很多,再此就不造轮子了。

本文主要记录put方法相关的几个点:

sizectl的作用?初始化tab数组时,如何保证线程安全的?初始化tab[i]的时候,如何保证线程安全的?遍历的时候,如何保证线程安全?addCount,如何保证线程安全的。JDK7的乐观+悲观策略。

路漫漫其修远兮,吾将上下而求索!

sizeCtl的作用

首先看其定义,其中关键字时 volatile 保证了线程可见性。

该字段主要起到了一个 状态描述 和 扩容阈值作用。

等于 0: 表示默认值大于 0:扩容阈值等于 -1:正在初始化小于 -1: 多个线程正在扩容

在ConcurrentHashMap中的几个操作中就利用了该字段的状态做判定,接着看。

初始化数组

HashMap的结构最外层是数组,这点大家应该都知道了,那么在当这个数组为null的时候,需要对其初始化,CCHM(ConcurrentHashMap)是如何做的?

上述代码中,有3个关键:

if ((sc = sizeCtl) < 0)判定当前是否有线程正在初始化,如果有,那么通过Thread.yield()时间片。通过CAS赋值,代码U.compareAndSwapInt(this, SIZECTL, sc, -1).外层的while循环保证必须初始化成功再退出。

初始化tab[i]

初始化完成tab后,通过key的hashcode计算出当前值存在再下标为i的数组中了,第一次插入的时候,tab[i]肯定为null,那么如何初始化了?

其中casTabAt为:

标准的CAS赋值,保证只会被初始化一次。

遍历判定

找到对应的数组下标后,就需要进行遍历链表或者红黑树,找出该值是否已经存在。

java.util.concurrent.ConcurrentHashMap#putVal

大致代码如所述:很容易看出来,通过了关键字synchronized锁住了列表第一个值。

addCount

addCount的作用是维护 CCHM的size,其代码如下所示:

U.compareAndSwapLong通过这个很明显看出来是CAS

JDK7的乐观和悲观

最后简单提下JDK7在put和size的实现,很有趣,其内部是:先是乐观尝试,达到一定的阈值都没有成功就转为悲观的加锁操作。

scanAndLockForPut代码如下:

size的代码如下:

很有意思吧,先乐观后悲观。

总结

上述这些就是最近看JDK7和JDK8的源码的一些个人理解。

其实结合原理来看,并不难。

路漫漫其修远兮,吾将上下而求索!

原创不易,感谢关注、评论、转发、收藏!

查看原文 >>
相关文章