ConcurrentHashMap笔记
ConcurrentHashMap
是 Java 中用于实现线程安全的哈希表的数据结构。它位于 java.util.concurrent
包中,专门设计用于支持高并发的环境。与传统的 HashMap
和 Hashtable
相比,ConcurrentHashMap
提供了更高效的并发操作,特别是在多线程环境下,它能避免传统同步方法的性能瓶颈。
1. 基本概念
ConcurrentHashMap
在多线程环境中提供高效的线程安全机制,特别适合需要频繁读取和更新操作的场景。它通过细粒度的锁和分段锁机制,确保在高并发情况下不会出现性能瓶颈。
主要特点
- 分段锁机制:将哈希表分成多个段,每个段有一个独立的锁。多个线程可以并发地访问不同段的数据,从而减少了全表加锁的瓶颈。
- 非阻塞读取:
get()
方法不需要加锁,读取操作是非阻塞的,即使其他线程修改数据,读取线程也不会被阻塞。 - 支持高并发更新:通过细粒度锁和乐观锁机制,使得并发写操作不会导致性能显著下降。
- 线程安全的迭代器:
ConcurrentHashMap
提供的迭代器是弱一致性的,允许在迭代时进行并发修改操作。
2. 版本演进:JDK 7 与 JDK 8 的区别
2.1 JDK 7 中的 ConcurrentHashMap
在 JDK 7 中,ConcurrentHashMap
使用了 Segment 数组来实现分段锁。每个段内部是一个独立的哈希表,每个段有一个独立的锁。当进行读写操作时,线程只会锁住涉及的段,而不会锁住整个哈希表。
结构
- Segment 数组:
ConcurrentHashMap
被划分为多个Segment
,每个Segment
本质上是一个HashMap
,独立使用锁。 - 桶(Bucket):每个段内的桶存储键值对。为了支持高效查找和并发,通常会采用链表或红黑树。
锁机制
- 每个
Segment
拥有一个锁,这使得在多线程环境下不同段可以并行处理。 - 当操作一个键值对时,
ConcurrentHashMap
只会锁定相关的段,而不是整个哈希表。这样避免了传统Hashtable
的全表锁。
2.2 JDK 8 中的 ConcurrentHashMap
JDK 8 对 ConcurrentHashMap
进行了显著的优化,移除了 Segment
数组,并且在实现上做了很多改进,使得整体结构更加简洁和高效。
结构
- 无 Segment 数组:在 JDK 8 之后,
ConcurrentHashMap
不再使用Segment
数组,而是将所有数据存储在一个主哈希表中。 - 红黑树优化:当链表长度超过 8 时,
ConcurrentHashMap
会将链表转化为红黑树,以提高查找效率。 - CAS(Compare-And-Swap)操作:JDK 8 引入了 CAS 操作,使得写操作更加高效且原子化。通过
CAS
,并发操作可以通过无锁机制进行。
锁机制
- 分段锁:虽然
Segment
数组被移除,但ConcurrentHashMap
依然采用了分段锁机制。具体来说,ConcurrentHashMap
会根据哈希值分配桶(Bucket),并为每个桶加锁,而不是锁住整个哈希表。 - 无全表锁:不同于
Hashtable
,JDK 8 中的ConcurrentHashMap
不会全表加锁。并发的读操作依旧是无阻塞的。
3. ConcurrentHashMap
主要方法
3.1 put(K key, V value)
- 将指定的键值对放入
ConcurrentHashMap
中。如果键已存在,替换其值。 - 写操作会锁定桶所在的段,但不会影响其他段的数据。
3.2 get(Object key)
- 获取指定键对应的值。该操作是线程安全且非阻塞的,即使其他线程正在修改数据,读取操作也不会被阻塞。
3.3 remove(Object key)
- 移除指定键的键值对。该操作通过加锁保证线程安全,但仅锁定目标桶,不会影响其他桶的数据。
3.4 replace(K key, V oldValue, V newValue)
- 只有当当前值与指定的
oldValue
相等时,才会用newValue
替换它。这个操作是原子性的,确保线程安全。
3.5 compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
- 基于当前值和提供的
remappingFunction
,重新计算并更新指定键对应的值。如果该键不存在,compute
会插入新值。
4. 与 HashMap
和 Hashtable
的比较
4.1 ConcurrentHashMap
vs HashMap
特性 | HashMap | ConcurrentHashMap |
---|---|---|
线程安全性 | 非线程安全 | 线程安全 |
锁机制 | 不支持并发操作,单线程加锁 | 支持高并发,分段锁 |
get() 操作 | 非线程安全 | 线程安全,非阻塞 |
put() 操作 | 非线程安全 | 细粒度锁,仅锁定需要操作的段 |
性能 | 在单线程环境下高效 | 在并发环境下性能优异 |
4.2 ConcurrentHashMap
vs Hashtable
特性 | Hashtable | ConcurrentHashMap |
---|---|---|
线程安全性 | 线程安全 | 线程安全 |
锁机制 | 整个表加锁 | 细粒度锁,分段锁机制 |
get() 操作 | 阻塞操作 | 非阻塞操作 |
put() 操作 | 整表锁定 | 细粒度锁 |
性能 | 性能差,锁粒度大 | 性能优越,支持高并发 |
5. ConcurrentHashMap
的优点与局限
5.1 优点
- 高并发性:通过分段锁和细粒度锁机制,在并发环境下能够高效地处理读取和写入操作。
- 线程安全:
ConcurrentHashMap
设计为线程安全,多个线程可以同时执行get()
、put()
等操作而不需要担心数据一致性问题。 - 无阻塞读取:对于
get()
操作,ConcurrentHashMap
是非阻塞的,不会因为写操作而影响读取性能。 - 红黑树优化:在 JDK 8 中,对于链表过长的桶会使用红黑树来优化查找性能,避免了链表查找的 O(n) 时间复杂度。
5.2 局限
- 写操作性能受限:尽管
ConcurrentHashMap
提供了很好的并发读取性能,但在大量写操作时,仍然会受到锁的影响,特别是涉及到锁竞争时。 - 顺序问题:
ConcurrentHashMap
不保证键值对的遍历顺序。如果需要顺序遍历,可以考虑使用LinkedHashMap
。
6. 总结
ConcurrentHashMap
是一种专门用于多线程并发访问的哈希表实现。它通过细粒度锁机制,极大地提高了并发读写操作的效率,适用于需要高并发处理的场景。随着 JDK 版本的演进,ConcurrentHashMap
在设计上不断优化,特别是在 JDK 8 中,引入了更高效的锁机制和红黑树优化,使得其在并发环境下更加高效和可靠。