面试题答案
一键面试- 分段锁机制(早期版本):
- 原理:在Java 8之前,
ConcurrentHashMap
采用分段锁(Segment
)机制。它将数据分成多个段(Segment
),每个Segment
类似一个小型的HashMap
,并各自持有一把锁。 - 高并发读写优势:在高并发读写场景下,不同线程可以同时访问不同的
Segment
,而不会相互阻塞。例如,线程A对Segment1
进行写操作,线程B可以同时对Segment2
进行读操作,大大提高了并发性能。
- 原理:在Java 8之前,
- CAS + synchronized锁优化(Java 8及之后):
- 数组初始化:在初始化
ConcurrentHashMap
的数组时,使用Unsafe
类的compareAndSwapInt
(CAS)操作来确保数组的正确初始化。这种操作是原子性的,多个线程同时尝试初始化数组时,只有一个线程能够成功,避免了数据不一致。 - 链表和红黑树操作:
- 链表插入:当向链表中插入节点时,首先通过CAS操作尝试将新节点插入到链表头部。如果失败,说明有其他线程同时在操作,此时才使用
synchronized
关键字对链表头节点加锁,进行插入操作。 - 红黑树操作:在向红黑树中插入或删除节点时,同样先尝试使用CAS操作。若失败,对红黑树的根节点使用
synchronized
加锁,保证操作的原子性,避免数据不一致。
- 链表插入:当向链表中插入节点时,首先通过CAS操作尝试将新节点插入到链表头部。如果失败,说明有其他线程同时在操作,此时才使用
- 读操作:
- 无锁读:
ConcurrentHashMap
的读操作大部分是无锁的。因为数组、链表或红黑树的节点都使用volatile
关键字修饰,保证了内存可见性。当一个线程修改了某个节点的值,其他线程能够立即看到最新值,所以读操作不需要加锁,提高了并发读的性能。 - 特殊情况:在扩容期间,读操作可能会受到一定影响,但
ConcurrentHashMap
通过一些机制确保读操作能够正确获取数据,例如使用ForwardingNode
来指引读操作到新的数组位置。
- 无锁读:
- 数组初始化:在初始化
- 扩容机制:
- 部分扩容:在扩容时,
ConcurrentHashMap
不会一次性将所有数据迁移到新的数组,而是采用分段迁移的方式。每次只迁移一部分数据,这样在扩容过程中,其他未迁移的部分仍然可以正常读写,避免了长时间的阻塞,保证了高并发场景下的可用性。 - 同步控制:在扩容过程中,使用
sizeCtl
变量来控制扩容的状态和并发操作。通过CAS操作对sizeCtl
进行更新,确保只有一个线程能够发起扩容操作,并且多个线程在协助扩容时能够有序地进行数据迁移,避免数据丢失或不一致。
- 部分扩容:在扩容时,