微信公众号:BoomDev
如有问题或建议请留言
最近更新:2018-05-28
如何保证集合是线程安全的? ConcurrentHashMap 如何实现高效地线程安全
经典回答:
Java 提供了不同层面上的线程安全支持、除了 HashTable
同步容器外、还有同步包装器(Synchronized Wrapper
)、调用 Collections
工具类中的 Collections.synchronizedMap
但是这些都是利用粗力度的同步方式、在高并发下性能比较低:
更加普遍的方式是利用并发包下的线程安全容器:
- 并发容器、比如 ConcurrentHashMap、CopyOnWriteArrayList
- 线程安全队列 Queue/Deque 、如 ArrayBlockingQueue、SynchronousQueue
具体保证线程安全的方式是从简单的 synchronize 到更加精细化的方式、比如分离锁实现的 ConcurrentHashMap 并发容器.
扩展分析
- 理解基本的线程安全工具
- 早期并发并发编程中 Map 存在的问题、简单同步方式的不足
- ConcurrentHashMap 采用了哪些方式提高并发表现
- ConcurrentHashMap 自身演进
为什么需要 ConcurrentHashMap
HashTable 比较低效、它的实现基本都是将 put、get、size 等方法加上synchronized
这就导致所有同步操作都竞争同一把锁、大大降低了并发操作的效率、Hashtable 或者同步包装版本、都只是适合在非高度并发的场景下
ConcurrentHashMap 的设计实现其实一直在演化早期 ConcurrentHashMap 其实现是基于:
- 分离锁、也就是将内部进行分段(
Segment
)里面则是 HashEntry 的数组、和 HashMap 类似、哈希相同的条目以链的形式存放 - HashEntry 内部使用
volatile
的 Value 字段保证值的可见性、也利用了不可变对象(final
)的机制改进 Unsafe 的底层能力
早期 ConcurrentHashMap 核心是利用分段设计、在进行并发操作的时候只需要锁定相应段、这样就避免了类似 HashMap 整体同步的问题
从 JDK 7 源码可以看出 ConcurrentHashMap 在进行并发操作时:
- ConcurrentHashMap 会获取再入锁、保证数据一致性
- 在最初时进行重复性扫描、确定相应 Key 值是否在数组中、决定是更新还是放置操作、重复扫描和检测冲突是 ConcurrentHashMap 的常用操作
- ConcurrentHaspMap 是单独对 Segment 进行扩容
在 Java 8 和之后的版本中 ConcurrentHashMap 发生了的变化:
- 总体上、它的内部存储结构和 HashMap 非常相似、同样是大的桶(bucket)数组、内部是链表结构(bin)、同步粒度更细致
- 内部仍然有 Segment 定义、仅仅是为了保证序列化的兼容、不再有结构上的用处
- 因为不再使用 Segment、初始化大大简化、采用 lazy-load 形式
- 数据存储采用 volatile 保证可见性
- 使用 CAS 等操作、特定场所进行无锁操作
- 使用 Unsafe、LongAdder 之类底层手段、进行极端情况优化
CAS(Compare and Swap)即比较并替换、实现并发算法时常用到的一种技术
我是一名有备而来的 Android 开发者
微信公众号:BoomDev
欢迎关注我、一起学习、一起进步!