ConcurrentHashMap解析
来源:互联网 发布:python串口数据采集 编辑:程序博客网 时间:2024/05/15 07:14
同步容器类在执行每个操作期间都会持有一个锁,有时当持有锁花费时间较长,此时其它线程在这段时间内都不能访问该容器,从而导致性能下降。
ConcurrentHashMap与HashMap一样,也是一个基于散列的Map,但它使用了一种完全不同的加锁策略来提供更高的并发性和伸缩性。ConcurrentHashMap使用粒度更细的称为分段锁(Lock Striping)的加锁机制实现更大程度的共享。
分段锁将数据分成一个一个的Segment来存储,并为每个Segment关联一个锁,这样当一个线程访问某一Segment时,其它Segment也能被其它线程访问,这样就实现了任意数量的读取线程可以并发访问Map,执行读取操作的线程和执行写入操作的线程可以并发地访问Map,并且一定数量的写入线程可以并发地修改Map。
ConcurrentHashMap内部结构图如下:
可以看到,ConcurrentHashMap内部包含很多Segment(默认为16个),每个Segment都继承自ReentrantLock类。
基本操作
构造函数
ConcurrentHashMap共有5个构造函数,源码如下:
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) throw new IllegalArgumentException(); if (concurrencyLevel > MAX_SEGMENTS) concurrencyLevel = MAX_SEGMENTS; // Find power-of-two sizes best matching arguments int sshift = 0; int ssize = 1; while (ssize < concurrencyLevel) { ++sshift; ssize <<= 1; } segmentShift = 32 - sshift; segmentMask = ssize - 1; this.segments = Segment.newArray(ssize); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; int c = initialCapacity / ssize; if (c * ssize < initialCapacity) ++c; int cap = 1; while (cap < c) cap <<= 1; for (int i = 0; i < this.segments.length; ++i) this.segments[i] = new Segment<K,V>(cap, loadFactor);}传入的参数有initialCapacity,loadFactor以及concurrentLevel三个:
- initialCapacity表示初始容量,即Entry的个数,默认为16。
- loadFactor表示负载因子,当Map中的元素大于loadFactor与最大容量的乘积时就需要rehash扩容,默认为0.75f。
- concurrentLevel表示并发级别,用来确定Segment的个数,Segment的个数是大于或等于ConcurrentLevel的第一个2的平方数;理想情况下,ConcurrentHashMap的并发访问线程数量能够到达concurrentLevel,因为至少有concurrentLevel个Segment。
put操作
put方法的源码如下:
public V put(K key, V value) { if (value == null) throw new NullPointerException(); int hash = hash(key.hashCode()); return segmentFor(hash).put(key, hash, value, false);}
final Segment<K,V> segmentFor(int hash) { return segments[(hash >>> segmentShift) & segmentMask];}
size操作
size操作与put和get操作最大的区别在于,size操作需要遍历所有的Segment才能算出整个Map的大小,而put和get都只关心一个Segment。假设我们当前遍历的Segment为SA,那么在遍历SA过程中其他的Segment比如SB可能会被修改,于是这一次运算出来的size值可能并不是Map当前的真正大小。一个比较简单的办法就是计算Map大小的时候将所有的Segment都Lock住,不能更新(包含put,remove等等)数据,计算完之后再Unlock,但这样会影响程序的性能。在JDK源码采用了一个更高效的办法:先给3次机会,不lock所有的Segment,遍历所有Segment,累加各个Segment的大小得到整个Map的大小,如果某相邻的两次计算获取的所有Segment的更新的次数(每个Segment都有一个modCount变量,这个变量在Segment中的Entry被修改时会加一,通过这个值可以得到每个Segment的更新操作的次数)是一样的,说明计算过程中没有更新操作,则直接返回这个值。如果这三次不加锁的计算过程中Map的更新次数有变化,则之后的计算先对所有的Segment加锁,再遍历所有Segment计算Map大小,最后再解锁所有Segment。源代码如下:
public int size() { final Segment<K,V>[] segments = this.segments; long sum = 0; long check = 0; int[] mc = new int[segments.length]; // Try a few times to get accurate count. On failure due to // continuous async changes in table, resort to locking. for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) { check = 0; sum = 0; int mcsum = 0; for (int i = 0; i < segments.length; ++i) { sum += segments[i].count; mcsum += mc[i] = segments[i].modCount; } if (mcsum != 0) { for (int i = 0; i < segments.length; ++i) { check += segments[i].count; if (mc[i] != segments[i].modCount) { check = -1; // force retry break; } } } if (check == sum) break; } if (check != sum) { // Resort to locking all segments sum = 0; for (int i = 0; i < segments.length; ++i) segments[i].lock(); for (int i = 0; i < segments.length; ++i) sum += segments[i].count; for (int i = 0; i < segments.length; ++i) segments[i].unlock(); } if (sum > Integer.MAX_VALUE) return Integer.MAX_VALUE; else return (int)sum;}
举例来说:一个Map有4个Segment,标记为S1,S2,S3,S4,现在我们要获取Map的size。计算过程是这样的:第一次计算,不对S1,S2,S3,S4加锁,遍历所有的Segment,假设每个Segment的大小分别为1,2,3,4,更新操作次数分别为:2,2,3,1,则这次计算可以得到Map的总大小为1+2+3+4=10,总共更新操作次数为2+2+3+1=8;第二次计算,不对S1,S2,S3,S4加锁,遍历所有Segment,假设这次每个Segment的大小变成了2,2,3,4,更新次数分别为3,2,3,1,因为两次计算得到的Map更新次数不一致(第一次是8,第二次是9)则可以断定这段时间Map数据被更新,则此时应该再试一次;第三次计算,不对S1,S2,S3,S4加锁,遍历所有Segment,假设每个Segment的更新操作次数还是为3,2,3,1,则因为第二次计算和第三次计算得到的Map的更新操作的次数是一致的,就能说明第二次计算和第三次计算这段时间内Map数据没有被更新,此时可以直接返回第三次计算得到的Map的大小。最坏的情况:第三次计算得到的数据更新次数和第二次也不一样,则只能先对所有Segment加锁再计算最后解锁。
- ConcurrentHashMap解析
- ConcurrentHashMap解析
- ConcurrentHashMap解析
- ConcurrentHashMap解析
- ConcurrentHashMap解析
- ConcurrentHashMap解析
- ConcurrentHashMap解析
- ConcurrentHashMap解析
- ConcurrentHashMap解析
- ConcurrentHashMap解析
- ConcurrentHashMap解析
- ConcurrentHashMap解析
- ConcurrentHashmap 解析
- ConcurrentHashMap解析
- ConcurrentHashMap解析
- [ZT] ConcurrentHashMap解析
- ConcurrentHashMap原理解析
- Java中ConcurrentHashMap解析
- -[__NSCFNumber rangeOfCharacterFromSet:]: unrecognized selector sent to instance 0x7fa5216589d0
- Kernel如何解析u-boot传过来的参数
- Java之Pcap文件解析(三:解析文件)
- String 中== 、equal和null
- Qt学习之parent参数
- ConcurrentHashMap解析
- UIView于CALayer的主要的关系
- FastDFS+nginx---(二)安装配置测试
- 关于嵌入式学习和开发需要注意的十三大法则
- 2016-01-10 smali/baksmali v2.1.1
- SSH隧道浅析
- Fragment实现懒加载
- 编译错:The method createSqlQuery(String, List<Object>) is ambiguous for the type
- Android——实践自定义UI-ViewGroup