JDK1.7 ConcurrentHashMap主要是Segment+HashEntry分段锁实现的
结构上,ConcurrentHashMap1.7 里面包含一个Segment数组,Segment继承了ReentrantLock,Segment则包含HashEntry的数组,HashEntry本身是一个链表结构,具有保存key,value的能力并能指向下一个节点的指针。
实际上,相当于每个Segment都是一个HashMap,默认它的长度是16。也就是支持16个线程的并发读写,Segment之间互不受影响。
Put 流程
其实整个流程类似HashMap,只不过是先定位到具体的Segment,然后通过ReentrantLock去操作
1. 计算hash,定位到segment,segment如果是空就先初始化
2. 使用ReentrantLock加锁,如果获取锁失败则尝试自旋,自旋超过次数就阻塞获取,保证一定获取锁成功
3. 遍历HashEntry,就是和HashMap一样,数组中key和hash一样就直接替换,不存在就再插入链表,链表同样
Get流程
key通过hash定位到segment,再遍历链表定位到具体的元素上,需要注意的是value是volatile的,所以get是不需要加锁的。
JDK1.8 ConcurrentHashMap主要是CAS+synchronized+Node+红黑树实现的
结构上, ConcurrentHashMap 1.8 抛弃了Segment分段锁机制,采用Node + CAS + Synchronized来保证并发安全进行实现,采用数组+链表+红黑树的存储结构。以数组元素作为锁,利用CAS+Synchronized来保证并发更新的安全,从而实现了对每个数组元素(Node)进行加锁,进一步减少并发冲突的概率。
Put流程
1. 首先先计算hash,遍历node数组,如果node是空的话,就通过CAS+自旋的方式初始化
2. 如果当前数组位置是空则直接通过CAS自旋写入数据
3. 如果hash==MOVED,说明需要扩容,执行扩容
4. 如果都不满足,就使用synchronized写入数据,写入数据同样判断链表、红黑树,链表写入和HashMap的方式一样,key hash一样就覆盖,反之就尾插法,链表长度超过8就转换成红黑树
Get流程
通过key计算hash,如果key hash相同就返回,如果是红黑树按照红黑树获取,都不是就遍历链表获取。