ConcurrentHashMap的设计
1.7和1.8的结构差异
1.7
- 结构
segment[]+hashEntry[],每个segment继承reentrantlock - 并发度
默认16个segment,理论最大并发16 - 缺陷
并发度固定,两次哈希查询慢
1.8
- 结构
Node[]+链表+红黑树,类似hashmap - 并发度
动态,理论可达数组长度 - 优势
一次哈希,红黑树优化(O(log n))
为什么能抛弃 Segment?
- CAS的成熟
CPU指令毫秒级完成 - synchorized升级
偏向锁—>轻量锁->重量锁,性能不亚于ReentrantLock - 红黑树引入
哈希冲突时查询O(lon n) - 分段计数
LongAdder思想,无锁统计size
put 方法在并发下具体是怎么保证线程安全的(
CAS + synchronized 的三道防线
for (;;) { // 自旋
if (tab == null) initTable(); // ① CAS 初始化
else if (桶为空) casTabAt(插入); // ② CAS 无锁插入(性能最高)
else if (扩容中) helpTransfer(); // ③ 协助并发扩容
else synchronized(头节点) { 插入链表/树 } // ④ 细粒度锁桶
}
- 场景一:数组初始化
CAS更新sizeCtl,(sizeCtl是表示目前数组状态的字段,volite修饰,扩容中,初始化等等) - 场景二:桶为空
CAS插入首节点,无锁、极速 - 场景三:桶非空
synchronized 锁头节点,粒度最细,不同桶互不影响
get 为什么全程不用加锁?
get方法不加锁靠三层机制保障
- volatile
Node.val和next用volatile修饰,写立即可见 - final
Node.key和hash不可变 - Happen-before
volatile写在读之前发送
size():分段计数,无锁统计
volatile long baseCount; // 基础计数
volatile CounterCell[] counterCells; // 辅助计数槽(≤ CPU核心数)
volatile int cellsBusy; // 扩容控制(CAS)
- 流程一
优先CAS更新BaseCount - 流程二
通过线程的Probe随机定位到某个CounterCell槽位,CAS累加 - 流程三
再失败,扩容CounterCell或更换Probe重试
原创
ConcurrentHashMap的设计
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法