2021-05-10阿里P7大佬!王者级讲解ConcurrentHashMap源码,码农:太透彻了

上一篇是分享的是《Scala - 类的定义》,这篇给大家分享《ConcurrentHashMap源码》。今天先更新一部分,一会儿要出门,谅解。

ConcurrentHashMap源码解读一

首先就先来说一下几个全局变量

private static final int MAXIMUM_CAPACITY=1<<30;//最大容量2的30次方

private static final int DEFAULT_CAPACITY=16;//默认容量  1<<4

private static final float LOAD_FACTOR=0.75f;//负载因子

static final int TREEIFY_THRESHOLD=8;//链表转为红黑树,大于8小于6先对链表数组进行翻倍扩容操作

static final int UNTREEIFY_THRESHOLD=6;//树转列表

static final int MIN_TREEIFY_CAPACITY=64;//链表真正转为红黑树

private static final int MIN_TRANSFER_STRIDE=16;

private static int RESIZE_STAMP_BITS=16;//stamp高位标识移动位数

private static final int MAX_RESIZERS=(1<<(32-RESIZE_STAMP_BITS))-1;

private static final int RESIZE_STAMP_SHIFT=32-RESIZE_STAMP_BITS;

static final int MOVED=-1;// forwarding nodes 的hash值,如果hash值等于-1代表线程协助扩容

static final int TREEBIN=-2;// roots of trees 的hash值,如果hash等于-2代表,当前桶是红黑树

static final int RESERVED=-3;// transient reservations 的hash值

// usable bits of normal node hash,在hash计算的时候运用到,与HashMap计算出来的hash值进行与操作

static final int HASH_BITS=0x7fffffff;

static final int NCPU=Runtime.getRuntime().availableProcessors();//可用处理器数量

然后是几个全局属性

transient volatile Node[]table;//当前ConcurrentHashmap的Node数组,正在使用的数组

private transient volatile Node[]nextTable;//ForwardNode所指向的下一个表,正在扩容的数组(还未使用)

private transient volatile long baseCount;//如果使用CAS计数成功,使用该值进行累加,计数用的

//扩容设置的参数,默认为0,当值=-1的时候,代表当前有线程正在进行扩容操作

//当值等于-n的时候,代表有n个线程一起扩容,其中n-1线程是协助扩容

//当在初始化的时候指定了大小,这会将这个大小保存在sizeCtl中,大小为数组的0.75

private transient volatile int sizeCtl;//标记状态以及数组阈值

private transient volatile int transferIndex;//数组扩容的时候用到

private transient volatile int cellsBusy;

//如果使用CAS计算失败,也就是说当前处于高并发的情况下,那么

//就会使用CounterCell[]数组进行计数,类似jdk1.7分段锁的形式,锁住一个segment

//最后size()方法统计出来的大小是baseCount和counterCells数组的总和

private transient volatile CounterCell[]counterCells;//计数数组。

首先是有参构造,这里如果是

ConcurrentHashMap chm=newConcurrentHashMap(15);

那么其实容量不是15,而是32;

publicConcurrentHashMap(int initialCapacity){

if(initialCapacity<0)

thrownewIllegalArgumentException();

int cap=((initialCapacity>=(MAXIMUM_CAPACITY>>>1))?

MAXIMUM_CAPACITY:

tableSizeFor(initialCapacity+(initialCapacity>>>1)+1));

this.sizeCtl=cap;

}

从这里可以看出是看tableSizeFor这个方法的,15+7+1=23;

private static final inttableSizeFor(int c){

int n=c-1;//23-1=22  0b10110

n|=n>>>1;// 10110 | 01011 = 11111,下面都是11111也就是31

n|=n>>>2;

n|=n>>>4;

n|=n>>>8;

n|=n>>>16;

return(n<0)?1:(n>=MAXIMUM_CAPACITY)?MAXIMUM_CAPACITY:n+1;//31+1 =  32

}

所以这就是最后的容量,为32,也就是有参的参数的两倍最近的2的次方数。

接下来就将put方法

final VputVal(K key,V value,boolean onlyIfAbsent){//onlyIfAbsent跟hashmap一样,就是判断是否要覆盖,默认为false,覆盖。

if(key==null||value==null)thrownewNullPointerException();////这句话可以看出,ConcurrentHashMap中不允许存在空值,这个是跟HashMap的区别之一 //通过这个机制,我们可以通过get方法获取一个key,如果抛出异常,说明这个key不存在

int hash=spread(key.hashCode());//这个方法就相当于基于计算hash值。

int binCount=0;//这个是记录这个桶的元素个数,目的是用它来判断是否需要转换红黑树,

for(Node[]tab=table;;){

Node f;int n,i,fh;//情况一:如果数组为空或者长度为0,进行初始化工作

if(tab==null||(n=tab.length)==0)

tab=initTable();//情况二:如果获取的位置的节点为空,说明是首节点插入情况,也就是该桶位置没有元素,利用cas将元素添加。

elseif((f=tabAt(tab,i=(n-1)&hash))==null){

if(casTabAt(tab,i,null,//cas加自旋(和外侧的for构成自旋循环),保证元素添加安全

newNode(hash,key,value,null)))//如果加成功了,那么就break,否则再经过for的死循环进行判断

break;// no lock when adding to empty bin

}//情况三:如果hash计算得到的桶的位置元素的hash值为moved,也就是-1,证明正在扩容,那么就协助扩容。

elseif((fh=f.hash)==MOVED)

tab=helpTransfer(tab,f);//hash计算的桶位置元素不为空,且当前没有处于扩容操作,进行元素添加        //情况四:这个桶有元素,则执行插入操作,有两种可能,一是这个桶对应的链表没有相同的key,那么久再链表尾插入node节点,而是有相同的key,那么久替换其value。

else{

V oldVal=null;

synchronized(f){//对当前桶进行加锁,保证线程安全,执行元素添加操作 //将桶位置的元素锁住,那么在该桶位中的链表或者红黑树进行添加元素的话,就是安全的,只有这个线程拿住了这个锁

if(tabAt(tab,i)==f){//因为添加元素之后可能链表已经变成红黑树了,那么这个f就可能变化了。所以要再进行判断。

if(fh>=0){//说明是普通链表节点

binCount=1;

for(Node e=f;;++binCount){

K ek;

if(e.hash==hash&&

((ek=e.key)==key||

(ek!=null&&key.equals(ek)))){//进行节点判断,如果都一样,那么覆盖旧值。

oldVal=e.val;

if(!onlyIfAbsent)

e.val=value;

break;

}

Node pred=e;

if((e=e.next)==null){//尾插法,如果e的下一个不是null,那么循环会让pred变成e,直到最后节点,此时e的下一个为null的话                            //那么也就是pred下一个为null,那么插入到pred下一个即可。

pred.next=newNode(hash,key,

value,null);

break;

}

}

}

elseif(finstanceofTreeBin){//树节点,将元素添加到红黑树中

Node p;

binCount=2;

if((p=((TreeBin)f).putTreeVal(hash,key,

value))!=null){

oldVal=p.val;

if(!onlyIfAbsent)

p.val=value;

}

}

}

}

if(binCount!=0){

if(binCount>=TREEIFY_THRESHOLD)//链表长度大于/等于8,有可能将链表转成红黑树,因为在treeifyBin(tab, i);方法中还有一个判断数组长度是否小于64的判断,如果小于64,就不会                //树化。只是数组扩容。

treeifyBin(tab,i);

if(oldVal!=null)//如果是重复键,直接将旧值返回

returnoldVal;

break;

}

}

}

addCount(1L,binCount);//添加的是新元素,维护集合长度,并判断是否要进行扩容操作

returnnull;

}

总结:并发map,jdk1.8的情况下,底层跟hashmap一样,也是数组加链表加红黑树。

以上就是《ConcurrentHashMap源码解读一》的分享。

也欢迎大家交流探讨,该文章若有不正确的地方,希望大家多多包涵。

你们的支持就是我最大的动力,如果对大家有帮忙给个赞哦~~~

你可能感兴趣的:(2021-05-10阿里P7大佬!王者级讲解ConcurrentHashMap源码,码农:太透彻了)