ConcurrentMap原理详解
jdk1.8
数据结构
-
数组+链表+红黑树
-
Node<K,V>{}
static class Node<K,V> implements Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
如何线程安全
-
数组桶值设置和更新使用CAS算法
- tabAt(Node<K,V>[] tab, int i)
- casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v)
- setTabAt(Node<K,V>[] tab, int i, Node<K,V> v)
-
针对链表和树更新使用Synchronize
设置putVal(K key, V value, boolean onlyIfAbsent)
-
1、数组为空,先初始化
-
2、tabAt获取桶位置元素,如果为空,则casTabAt(),期望值为null,新值为新节点
-
3、桶位置如果存在数据,说明hash冲突了,如果当前节点hash为MOVED说明在扩容,则当前线程辅助扩容
-
4、如果hash值>=0,说明是链表结构
- 4.1循环覆盖链表所有key一样节点
- 4.2循环至链表尾结点时(next为null)则追加到尾结点
-
5、如果当前节点对象属于TreeBin类型,则为红黑树
- 设置树节点
-
6、如果链表节点数量大于等于8时,则链表转红黑树
-
7、检查桶节点数量是否大于阈值,否则进行扩容
获取get(不加锁)
-
1、计算hash,tabAt获取桶位置是否为null
- 如果桶节点key和查询key一样则返回节点对应val
- 如果hash<0则为树节点,从树中找到key一样的值
- 反之从链表循环判断至key一样时返回
扩容transfer(并发)
-
扩容时机
- put完成后检验当前桶数量是否大于阈值
- put时新增节点时,如果节点数量大于8时转树,调用tryPresize方法,如果当前桶数量小于64时会扩容两倍,但此时不会转为树结构
- 扩容状态下 针对增删改时,如果操作节点是 ForwardingNode节点时会调用helpTransfer 辅助扩容
-
transfer
- 1、单线程新建一个两倍大小的节点数组
FAQ
-
如何保证线程安全
- unsafe cas主要负责并发修改桶位置的数据
- synchronize保证链表或者红黑树节点更新
-
与1.7的区别
- 8没有使用分段锁,而是使用锁力度更细的桶锁
- 支持多线程同时扩容,如果当前hash值为-1时,说明正在扩容,当前线程会加入帮助扩容,1.7也支持多线程扩容,因为每个分段相当于一个线程扩容,但是性能没有1.8高,因为1.8任意一个线程都会帮助扩容
- size计算不同,jdk8使用countercell帮助计数,1.7是每个分段内部维护一个size,然后遍历每个分段加锁进行统计
jdk1.7
分段数组+链表
XMind - Trial Version
来源:oschina
链接:https://my.oschina.net/u/4323284/blog/4926498