1、无论JDK1.7和JDK1.8中的ConcurrentHashMap的size()方法都是线程安全的
2、为什么那
JDK1.7的ConCurrentHashMap的size方法,先不加锁获取一次size长度,然后再获取一次,最多获取三次,比较前后两次获取的值,如果两个获取的值相同则不存在竞争的操作,线程安全,直接返回值就行;但是如果前后两次获取的值不同,那么会将每个Segment都加上锁,然后计算ConcurrentHashMap的size的值(Segment加锁保证线程安全)
JDK1.8的ConCurrentHashMap的size方法源码如下:
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
这个方法最大返回的是int最大值,但是ConcurrentHashMap的返回值有可能超过int的最大值;
JDK1.8中增加了mappingCount()方法,这个方法的返回值是long,所以JDK1.8推荐使用mappingCount获取map中数据的数量。
public long mappingCount() {
long n = sumCount();
return (n < 0L) ? 0L : n; // ignore transient negative values
}
无论size()方法还是mappingCount()方法,核心方法都是sumCount()
源码如下:
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;
}
}
return sum;
}
在sumCount()方法中可以看出当counterCells为空,直接返回baseCount,当counterCells不为空时返回的是遍历累计并加上baseCount
baseCount源码:
/**
* Base counter value, used mainly when there is no contention,
* but also as a fallback during table initialization
* races. Updated via CAS.
*/
private transient volatile long baseCount;
baseCount是一个volatile变量,在put()方法执行时怎么使用的baseCount的,在put()方法中的最后调用addCount()方法,addCount()源码如下:
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
if ((as = counterCells) != null ||
//1、CAS自增操作
!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 ||
//2、baseCount 失败进入countercells CAS
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
//3、都失败进入fullAddCount自旋,直到成功
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
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);
}
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
首先对baseCount做CAS自增操作
如果并发导致baseCount的CAS失败,则使用counterCells进行CAS,如果counterCells也失败
则会进入fullAddCount()方法,fullAddCount()方法进入自旋,直到成功为止
那么CounterCell到底是什么那?源码如下:
/**
* 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; }
}
是一个使用@sun.misc.Contended标记的类,内部是一个volatile修饰的变量。
@sun.misc.Contended注解是防止伪共享的
什么是伪共享:
缓存系统中是以缓存行(cache line)为单位存储的,缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节,当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。
所以伪共享对性能危害极大
JDK1.8版本之前没有这个注解,JDK1.8版本之后使用拼接的方式解决这一问题,把缓存行加满,让缓存直接的修改互不影响。
总结:
1、ConcurrentHashMap的size()方法是线程安全的
2、在JDK1.7版本Segment加锁的方法保证线程安全,JDK1.8版本之后,使用的是CAS和Volatile或synchronized来保证线程安全