正文
场景:线程A和线程B同时执行相同Segment对象的put方法
-
线程A执行tryLock()方法成功获取锁,则把HashEntry对象插入到相应的位置;
-
线程B获取锁失败,则执行scanAndLockForPut()方法,在scanAndLockForPut方法中,会通过重复执行tryLock()方法尝试获取锁,在多处理器环境下,重复次数为64,单处理器重复次数为1,当执行tryLock()方法的次数超过上限时,则执行lock()方法挂起线程B;
-
当线程A执行完插入操作时,会通过unlock()方法释放锁,接着唤醒线程B继续执行;
size实现
因为ConcurrentHashMap是可以并发插入数据的,所以在准确计算元素时存在一定的难度,一般的思路是统计每个Segment对象中的元素个数,然后进行累加,但是这种方式计算出来的结果并不一样的准确的,因为在计算后面几个Segment的元素个数时,已经计算过的Segment同时可能有数据的插入或则删除,在1.7的实现中,采用了如下方式:
try {
for (;;) {
if (retries++ == RETRIES_BEFORE_LOCK) {
for (int j = 0; j
ensureSegment(j).lock(); // force creation
}
sum = 0L;
size = 0;
overflow = false;
for (int j = 0; j
Segment
seg = segmentAt(segments, j);
if (seg != null) {
sum += seg.modCount;
int c = seg.count;
if (c
overflow = true;
}
}
if (sum == last)
break;
last = sum;
}
} finally {
if (retries > RETRIES_BEFORE_LOCK) {
for (int j = 0; j
segmentAt(segments, j).unlock();
}
}
先采用不加锁的方式,连续计算元素的个数,最多计算3次:
-
如果前后两次计算结果相同,则说明计算出来的元素个数是准确的;
-
如果前后两次计算结果都不同,则给每个Segment进行加锁,再计算一次元素的个数;
1.8实现
数据结构
1.8中放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,结构如下: