ConcurrentHashMap 总结( 下 )
来源:互联网 发布:淘宝好评改差评怎么改 编辑:程序博客网 时间:2024/05/17 13:41
转自:https://mp.weixin.qq.com/s?__biz=MjM5NzMyMjAwMA==&mid=2651479108&idx=3&sn=6f6a7cfa0093e3eba8306de6e42d5ecf&chksm=bd25303b8a52b92d1788bebfbdda5ff11a79afcd1604cdea79634dcdb3c3d5c3386f000dd25a&mpshare=1&scene=23&srcid=0925tKa60HZ2adVcJk2dT44u#rd
2.8 Size相关的方法
对于ConcurrentHashMap来说,这个table里到底装了多少东西其实是个不确定的数量,因为不可能在调用size()方法的时候像GC的“stop the world”一样让其他线程都停下来让你去统计,因此只能说这个数量是个估计值。对于这个估计值,ConcurrentHashMap也是大费周章才计算出来的。
2.8.1 辅助定义
为了统计元素个数,ConcurrentHashMap定义了一些变量和一个内部类
/**
* A padded cell for distributing counts. Adapted from LongAdder
* and Striped64. See their internal docs for explanation.
*/
@sun.misc.Contended static final class CounterCell {
volatile long value;
CounterCell(long x) { value = x; }
}
/******************************************/
/**
* 实际上保存的是hashmap中的元素个数 利用CAS锁进行更新
但它并不用返回当前hashmap的元素个数
*/
private transient volatile long baseCount;
/**
* Spinlock (locked via CAS) used when resizing and/or creating CounterCells.
*/
private transient volatile int cellsBusy;
/**
* Table of counter cells. When non-null, size is a power of 2.
*/
private transient volatile CounterCell[] counterCells;
2.8.2 mappingCount与Size方法
mappingCount与size方法的类似 从Java工程师给出的注释来看,应该使用mappingCount代替size方法 两个方法都没有直接返回basecount 而是统计一次这个值,而这个值其实也是一个大概的数值,因此可能在统计的时候有其他线程正在执行插入或删除操作。
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
/**
* Returns the number of mappings. This method should be used
* instead of {@link #size} because a ConcurrentHashMap may
* contain more mappings than can be represented as an int. The
* value returned is an estimate; the actual count may differ if
* there are concurrent insertions or removals.
*
* @return the number of mappings
* @since 1.8
*/
public long mappingCount() {
long n = sumCount();
return (n < 0L) ? 0L : n; // ignore transient negative values
}
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
long sum = baseCount;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;//所有counter的值求和
}
}
return sum;
}
2.8.3 addCount方法
在put方法结尾处调用了addCount方法,把当前ConcurrentHashMap的元素个数+1这个方法一共做了两件事,更新baseCount的值,检测是否进行扩容。
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
//利用CAS方法更新baseCount的值
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
//如果check值大于等于0 则需要检验是否需要进行扩容操作
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
int rs = resizeStamp(n);
//
if (sc < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
//如果已经有其他线程在执行扩容操作
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
//当前线程是唯一的或是第一个发起扩容的线程 此时nextTable=null
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
总结
JDK6,7中的ConcurrentHashmap主要使用Segment来实现减小锁粒度,把HashMap分割成若干个Segment,在put的时候需要锁住Segment,get时候不加锁,使用volatile来保证可见性,当要统计全局时(比如size),首先会尝试多次计算modcount来确定,这几次尝试中,是否有其他线程进行了修改操作,如果没有,则直接返回size。如果有,则需要依次锁住所有的Segment来计算。
jdk7中ConcurrentHashmap中,当长度过长碰撞会很频繁,链表的增改删查操作都会消耗很长的时间,影响性能,所以jdk8 中完全重写了concurrentHashmap,代码量从原来的1000多行变成了 6000多 行,实现上也和原来的分段式存储有很大的区别。
主要设计上的变化有以下几点:
不采用segment而采用node,锁住node来实现减小锁粒度。
设计了MOVED状态 当resize的中过程中 线程2还在put数据,线程2会帮助resize。
使用3个CAS操作来确保node的一些操作的原子性,这种方式代替了锁。
sizeCtl的不同值来代表不同含义,起到了控制的作用。
至于为什么JDK8中使用synchronized而不是ReentrantLock,我猜是因为JDK8中对synchronized有了足够的优化吧。
Reference:
1. http://www.jianshu.com/p/4806633fcc55
2. https://www.zhihu.com/question/22438589
3. http://blog.csdn.net/u010723709/article/details/48007881
- ConcurrentHashMap 总结( 下 )
- ConcurrentHashMap 总结( 下 )
- ConcurrentHashMap实现原理总结--下
- ConcurrentHashMap 总结( 上 )
- ConcurrentHashMap 总结( 中 )
- ConcurrentHashMap 总结( 上 )
- ConcurrentHashMap 总结( 中 )
- Java1.7下的ConcurrentHashMap【简要总结】
- Java1.8下的ConcurrentHashMap【简要总结】
- ConcurrentHashMap总结
- ConcurrentHashMap总结
- ConcurrentHashMap总结
- ConcurrentHashMap总结
- ConcurrentHashMap总结
- ConcurrentHashMap总结
- ConcurrentHashMap总结
- ConcurrentHashMap总结
- ConcurrentHashMap总结
- Activity的生命周期
- dubbo管理控制台安装和使用
- spark mongodb
- DBSCAN聚类
- 【安全牛学习笔记】w3af-身份认证
- ConcurrentHashMap 总结( 下 )
- 关于移动端响应式全屏背景图显示的问题
- 获取客户端信息
- 正则表达式(re)的基本操作
- 使用okhttp加载服务器数据,Glide加载图片
- 【codevs 3554】犯罪团伙
- Spring的控制反转(下)
- Objects are not valid as a React child 错误处理
- Java 标准 I/O 流编程一览笔录( 上 )