size 方法需要重点说明一下。爱思考的小伙伴可能就会想到,并发情况下,有可能在统计期间,数组元素个数不停的变化,而且,整个表还被分成了 N个 Segment,怎样统计才能保证结果的准确性呢? 我们一起来看下吧。
public int size() { // Try a few times to get accurate count. On failure due to // continuous async changes in table, resort to locking. //segment数组 final Segment<K,V>[] segments = this.segments; //统计所有Segment中元素的总个数 int size; //如果size大小超过32位,则标记为溢出为true boolean overflow; //统计每个Segment中的 modcount 之和 long sum; //上次记录的 sum 值 long last = 0L; //重试次数,初始化为 -1 int retries = -1; try { for (;;) { //如果超过重试次数,则不再重试,而是把所有Segment都加锁,再统计 size if (retries++ == RETRIES_BEFORE_LOCK) { for (int j = 0; j < segments.length; ++j) //强制加锁 ensureSegment(j).lock(); // force creation } sum = 0L; size = 0; overflow = false; //遍历所有Segment for (int j = 0; j < segments.length; ++j) { Segment<K,V> seg = segmentAt(segments, j); //若当前遍历到的Segment不为空,则统计它的 modCount 和 count 元素个数 if (seg != null) { //累加当前Segment的结构修改次数,如put,remove等操作都会影响modCount sum += seg.modCount; int c = seg.count; //若当前Segment的元素个数 c 小于0 或者 size 加上 c 的结果小于0,则认为溢出 //因为若超过了 int 最大值,就会返回负数 if (c < 0 || (size += c) < 0) overflow = true; } } //当此次尝试,统计的 sum 值和上次统计的值相同,则说明这段时间内, //并没有任何一个 Segment 的结构发生改变,就可以返回最后的统计结果 if (sum == last) break; //不相等,则说明有 Segment 结构发生了改变,则记录最新的结构变化次数之和 sum, //并赋值给 last,用于下次重试的比较。 last = sum; } } finally { //如果超过了指定重试次数,则说明表中的所有Segment都被加锁了,因此需要把它们都解锁 if (retries > RETRIES_BEFORE_LOCK) { for (int j = 0; j < segments.length; ++j) segmentAt(segments, j).unlock(); } } //若结果溢出,则返回 int 最大值,否则正常返回 size 值 return overflow ? Integer.MAX_VALUE : size; }其实源码中前两行的注释也说的非常清楚了。我们先采用乐观的方式,认为在统计 size 的过程中,并没有发生 put, remove 等会改变 Segment 结构的操作。 但是,如果发生了,就需要重试。如果重试2次都不成功(执行三次,第一次不能叫做重试),就只能强制把所有 Segment 都加锁之后,再统计了,以此来得到准确的结果。