章
目
录
Java面试题:谈谈你对ConcurrentHashMap的理解
得分点:
数组+链表+红黑树、锁的粒度
标准回答:
在JDK8中,ConcurrentHashMap
的底层数据结构与HashMap
类似,也采用了“数组+链表+红黑树”的结构。但它通过锁的粒度来实现线程安全,降低了锁的竞争范围,提高了并发性能。
1. 初始化数组或头节点时不加锁: 在初始化数组或头节点时,ConcurrentHashMap
并没有加锁,而是采用CAS(Compare and Swap)的方式进行原子替换操作。这使得初始化操作可以在多个线程之间并发执行,不会阻塞其他操作。
2. 锁的粒度是槽: 当插入数据时,ConcurrentHashMap
会进行加锁处理,但锁定的不是整个数组,而是某个槽中的头节点。这意味着锁的粒度是槽,而不是整个数组,因此多个线程可以同时操作不同的槽,从而提高了并发性能。
3. 扩容时的并发处理: 扩容时,ConcurrentHashMap
仍然会进行加锁处理,但锁定的仍然是槽中的头节点。多个线程可以同时参与数组的扩容工作,每个线程负责一段连续槽位的数据迁移任务。线程首先要通过CAS操作来争夺一段槽位的迁移权,获得任务后会锁定槽中的头节点,然后将链表或树中的数据迁移到新的数组中。
4. 查找数据时不加锁: 在查找数据时,ConcurrentHashMap
并不会加锁,因此查找操作的性能很好。在扩容过程中,仍然可以进行查找操作。如果某个槽位还未进行数据迁移,查找线程可以直接从旧数组中找到数据。如果某个槽位已经迁移完成,但整个扩容过程还未结束,扩容线程会创建一个转发节点存放在旧数组中,查找线程根据转发节点的提示,从新数组中找到目标数据。
在多线程并发扩容的情况下,ConcurrentHashMap
会合理分配迁移任务,考虑到硬件处理能力,平均分配任务量。同时,如果计算出的任务数量小于16,它会强制将任务数量设置为16,这是为了充分利用现代CPU的计算能力,避免任务过少浪费CPU资源。