结构模型
与HashMap不同的是,ConcurrentHashMap中多了一层数组结构
ConcurrentHashMap
类中包含两个静态内部类 HashEntry
和 Segment
。HashEntry
用来封装映射表的键 / 值对;Segment
用来充当锁的角色,每个 Segment
对象守护整个散列映射表的若干个桶。每个桶是由若干个 HashEntry
对象链接起来的链表。一个 ConcurrentHashMap
实例中包含由若干个 Segment
对象组成的数组
ConcurrentHashMap 在默认并发级别会创建包含 16个 Segment 对象的数组。每个 Segment 的成员对象 table 包含若干个散列表的桶。每个桶是由 HashEntry 链接起来的一个链表。如果键能均匀散列,每个 Segment 大约守护整个散列表中桶总数的 1/16。
ConcurrentHashMap 的结构示意图:
用分离锁实现多个线程间的并发写操作
这里的加锁操作是针对(键的 hash 值对应的)某个具体的 Segment,锁定的是该 Segment 而不是整个 ConcurrentHashMap。
用 Volatile 变量协调读写线程间的内存可见性
这点成就了:读线程在读取散列表时,基本不需要加锁
由于内存可见性
问题,未正确同步的情况下,写线程写入的值可能并不为后续的读线程可见。
只要之前对链表做结构性修改操作的写线程 M 在退出写方法前写 volatile 型变量 count,读线程 N 在读取这个 volatile 型变量 count 后,就一定能“看到”这些修改。所以读线程在读取散列表时,基本不需要加锁就能成功获得需要的值
总结
在使用锁来协调多线程间并发访问的模式下,减小对锁的竞争可以有效提高并发性。有两种方式可以减小对锁的竞争:
- 减小请求 同一个锁的 频率。
- 减少持有锁的 时间。
ConcurrentHashMap 的高并发性主要来自于三个方面:
- 用分离锁实现多个线程间的更深层次的共享访问。使用分离锁,减小了请求同一个锁的频率。
- 通过 HashEntery 对象的不变性及对同一个 Volatile 变量的读 / 写来协调内存可见性,使得读操作大多数时候不需要加锁就能成功获取到需要的值。
- 通过对同一个 Volatile 变量的写 / 读访问,协调不同线程间读 / 写操作的内存可见性。