ConcurrentHashMap在Java8中的实现改动较大,网上关于ConcurrentHashMap的文章也很少有基于java8的,将个人的一些理解记录下来以供分享。
目录
主要内部类
Node
ConcurrentHashMap底层是通过数组+链表(树)来实现的,数组中存储的就是Node。它与HashMap中的定义很相似,但是有一些差别它对value和next属性设置了volatile同步锁,它不允许调用setValue方法直接改变Node的value域,它增加了find方法辅助map.get()方法。
transient volatile Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
//当前节点的hash值
final int hash;
//当前节点的Key
final K key;
//当前节点的值,保证了可见性
volatile V val;
//下一个节点
volatile Node<K,V> next;
...
TreeNode
树节点类,另外一个核心的数据结构。 当链表长度过长的时候,会转换为TreeNode。 但是与HashMap不相同的是,它并不是直接转换为红黑树,而是把这些结点包装成TreeNode放在TreeBin对象中,由TreeBin完成对红黑树的包装。 而且TreeNode在ConcurrentHashMap继承自Node类,而并非HashMap中的集成自LinkedHashMap.Entry
TreeBin
这个类并不负责包装用户的key、value信息,而是包装的很多TreeNode节点。它代替了TreeNode的根节点,也就是说在实际的ConcurrentHashMap“数组”中,存放的是TreeBin对象,而不是TreeNode对象,这是与HashMap的区别。另外这个类还带有了读写锁。 这里仅贴出它的构造方法。可以看到在构造TreeBin节点时,仅仅指定了它的hash值为TREEBIN常量,这也就是个标识位
ForwardingNode
一个用于连接两个table的节点类。它包含一个nextTable指针,用于指向下一张表。而且这个节点的key value next指针全部为null,它的hash值为-1. 这里面定义的find的方法是从nextTable里进行查询节点,而不是以自身为头节点进行查找。通过名称也很容易理解这个节点的含义这个节点主要作用就是重定向,在resize过程中尽可能不影响对数据对读取。
Put方法
ConcurrentHashMap中key可为null吗?value可以为null吗?为什么呢?HashMap又是什么情况呢? 通过下面的代码可以发现,ConcurrentHashMap中的key和value都不能为null,这是因为在Node中计算hash的时候使用的是key.hashCode() ^ val.hashCode();而在HashMap中是Objects.hashCode(key) ^ Objects.hashCode(value);
流程图
- 计算key的hash值
- 如果没有初始化需要进行初始化
- 通过按位与进行快速取模计算出桶位置,如果该位置没有元素则通过CAS插入,如果该节点是FWD节点,则帮助完成扩容
- 如果有元素,也不是FWD节点则对该节点进行加锁后插入元素
- 插入元素后,进行元素计数加1,在该步操作中可能会出发扩容操作
-
源码分析
final V putVal(K key, V value, boolean onlyIfAbsent) {
//concurrentHashMap要求key,value都不能为null.为什么呢?这是因为 Node中计算hash的方法是key.hashCode() ^ val.hashCode();
//如果key或者value为null在此处计算hash值的时候会出现NPE
if (key == null || value == null) throw new NullPointerException();
//计算key的hash值(两次hash计算,通过掩码运算得到内部hash值)
int hash = spread(key.hashCode());
int binCount = 0;
//死循环直到操作成功
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
//此处如果tab=null,或者tab.length-0表示表没有初始化,此处需要完成初始化!
//这个就是ConcurrentHashMap将初始化延迟到第一次put操作时候完成
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//i=(n-1)&hash这个计算和HashMap相同,这个是对hash值对快速取模操作,i的值即表示该数据存放的桶号
//如果在i位置的桶中没有元素则通过CAS操作试图将元素插入到该位置,如果插入成功则退出循环!在向空桶中添加元素时是无锁操作
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null