中企动力技术支持网站,纵横中文网,注册公司注册,市场营销ppt模板算不上死磕#xff0c;里面太痛苦了#xff0c;现在很多位移等操作还看不懂#xff0c;只是先理清大致思路#xff0c;面试用 concurrentHashMap的实现原理
为啥会用到#xff1f;并发安全。之前都用的hashtable实现线程安全的map#xff0c;但是太过笨重#xff0c;不… 算不上死磕里面太痛苦了现在很多位移等操作还看不懂只是先理清大致思路面试用 concurrentHashMap的实现原理
为啥会用到并发安全。之前都用的hashtable实现线程安全的map但是太过笨重不管是put还是get都直接synchronized锁住。性能低。在jdk1.5 Doug Lea搞了个concurrentHashMap。
那它是如何实现线程安全呢 jdk1.7 采用reentrantLocksegment jdk1.8 采用 synchronizedcas
segment有什么好处给每个segment加锁当一个线程用一个锁访问其中一个段的时候其他段的数据也可以被其他线程访问实现真正的并发访问。
那1.8呢为啥改用synchronizedcas了底层是hashMap也就是数组链表红黑树ConcurrentHashMap采用Node类作为基本的存储单元每个键值对(key-value)都存储在一个Node中使用了volatile关键字修饰value和next保证并发的可见性。
put方法
流程 如果key或者value为null抛空指针异常这点和hashMap不同
static final int spread(int h) { // 和hashMap类似先扰动算法hash异或自身右移16位得到更离散的hash值 // 然后跟 32位最大正整数做与运算确保最高位是0避免hash值为1导致可能的数组越界return (h ^ (h 16)) HASH_BITS;
}如果table为null或者table的长度为0则初始化table里面也用到CAS自旋
if (tab null || (n tab.length) 0)tab initTable(); 不为空和hashMap类似找桶中的位置(n-1)hash 通过tableAt这个CAS方法查看该位置是否有元素没有casTabAt()方法CAS操作将元素插入到Hash表
else if ((f tabAt(tab, i (n - 1) hash)) null) {if (casTabAt(tab, i, null,new NodeK,V(hash, key, value, null))) // 桶内为空CAS 放入不加锁成功了就直接 break 跳出break; 如果有元素发生了hash冲突如果hashMOVED说明正在扩容resize否则使用synchronized同步块上锁当前节点Node插入也是三类如果相同value替换如果红黑树红黑树处理如果一直没得链表尾插。 执行完synchronized(f)同步代码块之后会检查binCount,如果大于等于TREEIFY_THRESHOLD 8则进行treeifyBin操作尝试将该链表转换为红黑树
最后执行了一个addCount方法,主要用于统计数量以及决定是否需要扩容在并发模式下没有用CAS计数而是分治的思想定义了一个数组来计数每次线程需要计数的时候都通过随机的方式获取一个数组下标的位置进行操作这样就可以尽可能的降低了锁的粒度最后获取 size 时则通过遍历数组来实现计数当然具体CounterCell里面也用到CAS。
扩容
addCount 在添加元素数量的同时也会判断当前 ConcurrentHashMap 的大小是否达到了扩容的阈值如果达到需要扩容。扩容也支持多线程同时进行。
满足扩容条件之后采用的是分段扩容法即每个线程负责一段默认最小是 16也就是说如果 ConcurrentHashMap 中只有 16 个槽位那么就只会有一个线程参与扩容。如果大于 16 则根据当前 CPU 数来进行分配最大参与扩容线程数不会超过 CPU 数
参考
ConcurrentHashMap面试十连问你能扛到第几问?面试为了进阿里死磕了ConcurrentHashMap源码和面试题(一)