概述
顾名思义,ConcurrentHashMap,是支持并发的HashMap。作为散列表,它采用的数据结构与HashMap基本一致,都是采取array+list(linkedlist)/tree(red black tree)的形式。作为支持并发的集合,与Hashtable简单的采取synchronized关键字实现同步,ConcurrentHashMap使用了更为复杂的机制,包括volatile变量、原子操作CAS、synchronized等。借此,ConcurrentHashMap在get()时不需要加锁,put()时也只是对应bin加锁,比Hashtable更快。由于ConcurrentHashMap的数据结构及其实现与HashMap相似,所以本文不再多述,而关注于并发实现。
导航
构造方法
CurrentHashMap的构造方法很简单,设置capacity(容量)、loadFactor(装载因子)、concurrencyLevel(并发数量,保留参数,实际上没有使用)等属性。同HashMap,CurrentHashMap也选择了延时初始化:在第一次put的时候进行初始化。
与HashMap相比,CurrentHashMap拥有一个控制并发的关键变量:sizeCtl。当map未初始化时,sizeCtl=初始化容量;初始化后,sizeCtl>0时,则代表着下次再散列的门槛容量,sizeCtl<0时,则代表map正在进行初始化或者再散列(-1表示正在初始化)
1 | public ConcurrentHashMap(int initialCapacity, |
get
get操作相对简单。
1 | public V get(Object key) { |
put
put时,如果未进行过初始化,将会initTable。如果当前的bin正在进行resize,则会帮助resize,直到整个resize完成,插入新值。
1 | public V put(K key, V value) { |
size
在高并发的情况下,只有一个线程能完成CAS,其他线程会不断的循环等待。如果简单的设置count字段来统计,无疑会产生较大的资源浪费。为此,ConcurrentHashMap使用了baseCount与CounterCell[] counterCells来进行count统计与处理。每个counterCell对应着线程的计数,并使用了@Contended注解来避免false sharing的发生。线程只有在尝试更新baseCount失败时,才会尝试去更新counterCell[current]。因此,size()的值由baseCount与counterCells共同决定。
1 | // 避免false sharing |
在put时,会调用addCount方法,对baseCount与counterCells进行操作。
1 | private final void addCount(long x, int check) { |
再散列
再散列入口在addcount方法中。
1 | private final void addCount(long x, int check) { |
TreeBin
相对于HashMap的table[] 直接存储tree的root节点,ConcurrentHashMap存的则是一个特别的结点:first。顾名思义,first指向TreeBin中第一个插入的结点
1 | TreeBin(TreeNode<K,V> b) { |
first结点与next字段的存在,treebin可以在等待或树正在进行重构时,进行顺序遍历来寻找元素。而root只有在真正需要加锁的时候(树重构)的时候才会被加锁,提高了根据root遍历的效率。
1 | static final class TreeBin<K,V> extends Node<K,V> { |
遍历
key、value、entry迭代器的实现类似,这里key来举例
1 | /** |
可见KeyIterator的实现是扩展了BaseIterator,而BaseIterator又扩展了Traverser。其中的关键方法hasNext()和next(),有来自Traverser的实现。
1 | static class Traverser<K,V> { |