ConcurrentHashMap1.7原理:
1.底层结构:数组(Segment)+ 数组(HashEntry)+ 链表(HashEntry节点)
2.ConcurrentHashMap使用了分段锁技术,当一个线程占用锁访问其中一个Segment时,不会影响访问其他Segment.
put()
1.先根据key的hash值,找到对应的Segment,在调用Segment的put()。
2.Segment的put()要加锁。(第一步先获取到锁,如果获取失败,说明有其他线程在竞争,之后自旋获取锁,自旋最多64次,到了最大次数还是没有获取锁,则改为阻塞锁获取,保证获取成功)
3.在Segment的put()中,首先先根据hash值找到头结点,如果找不到,就直接往Segment的table新增一个HashEntry,同时检查是否需要扩容;
4.如果找到了,就遍历链表,如果hash,key相等,就覆盖并且返回原值,如果找不到相等的,就直接在尾部插入一个新节点。
5.调用unlock()解除当前Segment的锁。
get()
1.get()不加锁;(HashEntry的value属性是由volatile修饰,因此保证了内存的可见性,所以取到的都是最新值,因此可以不加锁)
2.根据key的hash值,找到对应的Segment,在根据Segment的get()找到头结点,之后遍历链表,找到hash,key相等的,返回value
3.找不到头结点,或者遍历不到,就返回null。
ConcurrentHashMap1.8原理:
1.7虽然解决了并发问题,但是跟HashMap1.7一样,就是如果链表太长,会影响查询效率。因此1.8抛弃了Segment分段锁,采用CAS+Synchronized来保证并发安全性。1.8中对Synchronized 关键字进行了优化,提高了效率。
底层结构:
Node数组+链表/红黑树,和HashMap1.8类似。
put()
1.根据key得出hash值,之后判断是否需要初始化,找到头结点或者根节点,如果没有找到,就使用cas写入一个Node节点,写入失败就自旋保证成功。
2.该位置的hashcode==-1,说明需要扩容。
3.如果该位置不为空,用同步关键字加锁,那么hash>0,则是一个头结点,遍历比较,key,hash相等,覆盖返回原值,不同,尾部插入。
4.hash<0,这是一个根节点,按红黑树方式添加。
5.判断链表是否超过阈值,是否需要转化为红黑树结构。
get()
1.根据key的hash值找到位置,如果该位置为空,就返回空,如果不为空,那么hash>0,就按链表返回值,hash<0,就按红黑树返回值。
有CAS,还需要Synchronized干嘛?
cas在并发小的情况下,可以不用在用户态和内核态之间的线程上下文切换,同时自旋率降低,性能提升。但是在并发激烈的时候,自旋率激增,性能大大降低。