1.分段锁的设计目的
ConcurrentHashMap 是支持高并发的线程安全的 HashMap。相较于 HashTable 使用 synchronized 方法来保证线程安全,ConcurrentHashMap 采用分段锁的方式,在线程竞争激烈的情况下 ConcurrentHashMap 的效率高很多。
ConcurrentHashMap 中的分段锁称为 Segment,它的内部结构是维护一个 HashEntry 数组,同时 Segment 还继承了 ReentrantLock。
当需要 put 元素的时候,并不是对整个 ConcurrentHashMap 进行加锁,而是先通过 hashcode 来判断它放在哪一个分段中,然后对该分段进行加锁。所以当多线程 put 的时候,只要不是放在同一个分段中,就可以实现并行的插入。分段锁的设计目的就是为了细化锁的粒度,从而提高并发能力。
ConcurrentHashMap 数据结构模型
ConcurrentHashMap 中维护着一个 Segment 数组,Segment 中有维护着一个 HashEntry 的数组,所以 ConcurrentHashMap 的底层数据结构可以理解为:数组 + 数组 + 链表
ConcurrentHashMap 分段锁的实现
ConcurrentHashMap 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程操作不同的分段,就不会存在锁竞争,提高并发访问率。
Segment 本身就继承了 ReentrantLock 具备了锁的功能,在每次 put 前都会先尝试 tryLock() 加锁,如果成功则进行元素存储;如果失败,就会调用 Segment 的 scanAndLockForPut() 尝试循环加锁并扫描指定的 key。
put的完整流程
3.JDK1.8中的ConcurrentHashMap
jdk1.8 中的 ConcurrentHashMap 中废弃了 Segment 锁,直接使用了数组元素,数组中的每个元素都可以作为一个锁。在元素中没有值的情况下,可以直接通过 CAS 操作来设值,同时保证并发安全;如果元素里面已经存在值的话,那么就使用 synchronized 关键字对元素加锁,再进行之后的 hash 冲突处理。
jdk1.8 的 ConcurrentHashMap 加锁粒度比 jdk1.7 里的 Segment 来加锁粒度更细,并发性能更好。