- 再调用【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锁住,从而保证强一致性。