作者:馨海之洋_895 | 来源:互联网 | 2023-10-11 15:25
1.7中的ConcurrentHashMap的介绍
HashMap是一个线程不安全的集合,如果想要在并发下让他线程安全那么可以使用Hashtable,但是Hashtable是对整个集合进行加锁,这明显也不是我们想要的。按照历史经验来讲,要提升性能比较好想到的手段就是减小锁的粒度。
在ConcurrentHashMap1.7中使用了一组Segment集合作为作为锁,它的底层继承自ReentrantLock,Segment里面包含了一个HashEntry数组,HashEntry是一个链表结构的元素,每一个Segment分别锁定整个集合中的一部分数据,从而实现减小锁粒度提升并发能力的目的。
当发生并发更新的时候,首先求出key的hash值并定位到具体的segment,检查当前segment是否初始化,因为ConcurrentHashMap初始化时只会初始化第一个segment;之后对当前的segment尝试加锁,如果加锁失败将采用CAS进行自旋等待获取锁。
1.7中的ConcurrentHashMap是写锁而不是读写锁,所以在读数据的时候并没加锁,而是使用了volatile关键字修饰value,使其读的时候可以立马获取其他线程修改后的值。
在使用ConcurrentHashMap的时候,应该尽量避免使用size、containsValue因为最坏条件下ConcurrentHashMap可能会升级为全局锁。
1.8中的ConcurrentHashMap的介绍
1.7中的ConcurrentHashMap使用了分段锁的思想,要想实现更好的并发性就是继续减小索粒度。在1.8中的ConcurrentHashMap采用table数组元素作为锁,从而实现了更小的索粒度,进一步减少并发冲突概率。
这就让我感觉很多技术思想都是相通的,Hashtable,ConcurrentHashMap1.7到1.8很容易类比到MySQL的表锁,分段锁和行锁。都是尽可能的减小锁粒度从而提交并发性能。
1.7中的ConcurrentHashMap采用数组+单链表的数据结构对于一个hash表来说,最核心的能力就是将key hash之后能够均匀的分布,那么table数组中的每个队列长度主要为0或者1。但实际情况也并非如此理想,虽然ConcurrentHashMap类默认的加载因子是0.75,但是在数据量过大或者运气不佳的情况下,还是会存在一些队列长度过长的情况,如果还是采用单链表方式,那么某个节点的查询的时间复杂度就会退化成为O(n);因此对于超过8的列表,jdk1.8中采用了红黑树的结构,那么时间复杂度就可以降低到O(logN);