ConcurrentHashMap JDK1.8的 数据结构: 数组 + 链表(红黑树)
线程安全:在进行一些列操作的时候,多个线程进行操作和单个线程操作的结果不一致;
hashmap 1.8中出现线程安全的场景:
hash 算法(初始化之后hashMap的数组长度为16,hash算法首先得到key的整型数,然后控制这个整型数在0-15之间)只用于计算 不会在多线程的情况下出现问题
Hashmap 1.8中出现线程不安全的场景:
一. 数组初始化 Node[] table = new Node[16]; new HashMap的时候不会初始化数组,在元素调用put的时候初始化数组
二. put操作(put元素时,判断key是否为null)
(1)直接判断key是否为null
(2) key 不为null
①key相同 --> value进行覆盖操作
② key不相同(hash 相同) 链表(尾插) 链表长度大于8,存储结构转换成红黑树
③ 链表太长转换红黑树的存储
三. 扩容机制(整个数据结构中Node节点的数量超过 数组大小*0.75 时 触发扩容) 会有线程安全问题
① 仅仅下表有元素(newTab 会重新计算 数组下标的位置)
②下标有元素下面有链表
e.hash & oldCap == 0 只有链表的情况(扩容时)
链表分成两部分:一部分时按兵不动,第二部分是比原来的位置+oldCap(老数组的大小)
正常思路:e.hash & newCap-1 得到具体小标的位置
③下标有元素下面有红黑树
HashTable (synchronized )解决方案:(synchronized修饰)效率低
ConcurrentHashMap 线程安全的解决方案:
一. 数组初始化:compareAndSwapInt 使用CAS原理来保证线程安全。
二. put操作(put元素时,判断key是否为null)
(1) 利用tabAt(使用volatile)来确保拿到key的最新值,key为null时 再使用casTabAt的无锁化比较,进行赋值
以下情况利用synchronized加锁:
ConcurrentHashMap 在key值冲突操作链表时会使用 synchronized 进行加锁(锁的是当前key下的链表) 原因:
CAS在 并发操作下会出现不断的比较来判断是否可以获取锁而synchronized直接锁住对象;
①key相同 --> value进行覆盖操作
② key不相同(hash 相同) 链表(尾插) 链表长度大于8,存储结构转换成红黑树
③ 链表太长转换红黑树的存储
三. 扩容机制
put元素时扩容机制会在synchronized之外进行:
① 正常扩容(数组容量到达 数组长度*0.75):
扩容方法 : put --> putVal --> addCount --> transfer(tab, null)(扩容方法)
(1) 控制只有一个线程能够创建新的数组
transfer的第二个参数为null时,表示当前线程初始化数组(进行扩容)
在其他线程put时,判断MOVED 如果 = -1 会进行帮助扩容,transfer的第二个参数不为null
(2)多个线程同时进行搬移Node节点
put元素时判断是否在扩容,如果扩容就帮助它扩容
② 链表转红黑树:
链表长度 大于 8时:
(1) 链表长度 大于8 但 数组长度 小于 64 出发扩容机制(调用 transfer) --> 单纯的对链表进行扩容 这样比转红黑树性能消耗低
(2) 链表长度 大于8 但 数组长度 大于等于 64 转红黑树