【Java】【HashMap】JDK1.7中ConcurrentHashMap是如何解决调用size时的一致性问题呢?
- 再调用【size()】时,会出现什么样的一致性问题呢?
1》在没有put的情况下,调用concurrentHashMap的size()方法,可以直接获取到表的长度
2》在线程A调用size()方法的同时,线程B试图PUT了一个数据,那么线程A如何保证size()结果一致性呢?
- 当然,JDK设计人员一定也想到了这个问题,官方源码如下:
public int size() { // Try a few times to get accurate count. On failure due to // continuous async changes in table, resort to locking. final Segment<K,V>[] segments = this.segments; int size; boolean overflow; // true if size overflows 32 bits long sum; // sum of modCounts long last = 0L; // previous sum int retries = -1; // first iteration isn't retry try { for (;;) { if (retries++ == RETRIES_BEFORE_LOCK) {//5.判断尝试次数是否尝试最大值 for (int j = 0; j < segments.length; ++j)//6.对所有segment加锁,然后重新统计 ensureSegment(j).lock(); // force creation } sum = 0L; size = 0; overflow = false; for (int j = 0; j < segments.length; ++j) {//1.遍历所有Segment Segment<K,V> seg = segmentAt(segments, j); if (seg != null) { sum += seg.modCount; //3.把segment的修改次数累加起来 int c = seg.count; //2.把segment的元素数量累加起来 if (c < 0 || (size += c) < 0) overflow = true; } } if (sum == last)//4.判断segment总修改书是否大于上一次修改数 break; last = sum; } } finally { if (retries > RETRIES_BEFORE_LOCK) { for (int j = 0; j < segments.length; ++j) segmentAt(segments, j).unlock();//7.释放锁,统计结束 } } return overflow ? Integer.MAX_VALUE : size; }
- 单看源码一定很抽象,我们简述一下源码流程吧:
1》遍历所有的Segment。
2》把Segment的元素数量累加起来。
3》把Segment的修改次数累加起来。
4》判断所有Segment的总修改次数是否大于上一次的总修改次数。如果大于,说明统计过程中有修改,重新统计,尝试次数+1;如果不是。说明没有修改,统计结束。
5》如果尝试次数超过尝试最大值,则对每一个Segment加锁,再重新统计。
6》由于已经加锁,上一次修改次数和本次修改次数一定相等;然后释放锁,统计结束
- 总结一下设计思想:
整体流程成可以抽象理解成是由乐观锁转向悲观锁的一个过程。
先不锁住所有Segment,而是乐观的假设调用size()过程不会有修改。
当尝试一定次数依然有修改的话,才会悲观的将所有Segment锁住,从而保证强一致性。
学而不思则罔 思而不学则殆 !