1. 多线程环境下,如何实现一个 Hash 结构?
在多线程并发环境下,我们可以使用 ConcurrentHashMap 来实现这样一个需求。
2. 介绍一下 ConcurrentHashMap 的底层原理?
ConcurrentHashMap 的话要分为两种情况分析,一种是在 JDK1.7,一种是在 JDK8 之后,他们之间的差别是比较大的。
在 JDK1.7 的话:它的底层是使用数组 + 链表来实现的,使用分段锁来保证线程安全,它将数组分成 16 段,也就是给每个 Segment 来配一把锁,在读每个 Segment 的时候先获取对应的锁,所以它最多能有 16 个线程并发去操作。
JDK1.7 的 ConcurrentHashMap 介绍:
Segment 段
ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,所以要复杂一些。整个 ConcurrentHashMap 由一个个 Segment 组成,Segment 代表” 部分 “或” 一段 “的意思,所以很多地方都会将其描述为分段锁。
线程安全(Segment 继承 ReentrantLock 加锁)
简单理解就是,ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承
ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
并行度(默认 16)
concurrencyLevel:并行级别、并发数、Segment 数,怎么翻译不重要,理解它。默认是 16,也就是说 ConcurrentHashMap 有 16 个 Segments,所以理论上,这个时候,最多可以同时支持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上。这个值可以在初始化的时候设置为其他值,但是一旦初始化以后,它是不可以扩容的。再具体到每个 Segment 内部,其实每个 Segment 很像之前介绍的 HashMap,不过它要保证线程安全,所以处理起来要麻烦些。
到了 JDK1.8 之后:它跟 HashMap 一样,也引入了这种红黑树的结构。同时在并发处理方面,不再使用分段锁的方式,而是采用 CAS 加 synchronized 关键字这种方式来实现一种更加细粒度的锁,相当于是把这个锁控制在了更加细粒度的哈希桶的这个级别。然后在写入键值对的时候可以锁住哈希桶的这种链表的头节点,这样就不会影响到其他的哈希桶的写入,从而提高对并发的这种处理能力。