第10讲 | 如何保证集合是线程安全的? ConcurrentHashMap如何实现高效地线程安全?

如何保证容器是线程安全的? ConcurrentHashMap如何实现高效地线程安全?

Java提供了不同层面的线程安全支持。在传统集合框架内部,除了Hashtable等同步容器,还提供了所谓的同步包装器(Synchronized Wrapper),我们可以调用Collections工具类提供的包装方法,来获取一个同步的包装容器(如Collections.synchronizedMap),但是它们都是利用非常粗粒度的同步方式,在高并发情况下,性能比较低下。

另外,更加普遍的选择是利用并发包提供的线程安全容器类,它提供了:

  • 各种并发容器,比如ConcurrentHashMap、 CopyOnWriteArrayList。
  • 各种线程安全队列(Queue/Deque),如ArrayBlockingQueue、 SynchronousQueue。
  • 各种有序容器的线程安全版本等。

1.为什么需要ConcurrentHashMap?

Hashtable本身比较低效,因为它的实现基本就是将put、 get、 size等各种方法加上“synchronized”。简单来说,这就导致了所有并发操作都要竞争同一把锁,一个线程在进行同
步操作时,其他线程只能等待,大大降低了并发操作的效率

个人理解
就是更细粒度的加锁机制来实现更大程度的共享。

2.ConcurrentHashMap分析

ConcurrentHashMap的设计实现其实一直在演化。

1.7
  • put加锁
  • 通过分段加锁segment,一个hashmap里有若干个segment,每个segment里有若干个桶,桶里存放K-V形式的链表, put数据时通过key哈希得到该元素要添加到的segment,
  • 然后
    对segment进行加锁,然后在哈希,计算得到给元素要添加到的桶,然后遍历桶中的链表,替换或新增节点到桶中
  • size
    分段计算两次,两次结果相同则返回,否则对所以段加锁重新计算
1.8
  • put CAS 加锁
  • 1.8中不依赖与segment加锁,segment数量与桶数量一致;
  • 首先判断容器是否为空,为空则进行初始化利用volatile的sizeCtl作为互斥手段,如果发现竞争性的初始化,就暂停在那里,等待条件恢复,否则利用CAS设置排他标志
    (U.compareAndSwapInt(this, SIZECTL, sc, -1)) ;否则重试
  • 对key hash计算得到该key存放的桶位置,判断该桶是否为空,为空则利用CAS设置新节点
    否则使用synchronize加锁,遍历桶中数据,替换或新增加点到桶中
  • 最后判断是否需要转为红黑树,转换之前判断是否需要扩容
    size
    利用LongAdd累加计算
ConcurrentHashMap使用要点

ConcurrentHashMap使用要点

ConcurrentHashMap允许一边更新、一边遍历,也就是说在Iterator对象遍历的时候,ConcurrentHashMap也可以进行remove,put操作,且遍历的数据会随着remove,put操作产出变化

参考:
极客时间:《Java核心技术面试精讲》

本笔记根据专栏主题进行学习笔记,虽然参考了许多做了笔记,但是加上了自己的整理,跟原作者的行文可能有很大偏差。如果想查看原版请自行搜索。谢谢
————————————————

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值