除了线程安全之外,ConcurrentHashMap与HashMap大致一致,ConcurrentHashMap1.8跟1.7不一样,1.7由segment+HashEntry组成,1.8是由数组+链表+红黑树,倒是跟1.8的HashMap有点像
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable {}
常量
/* ---------------- Constants -------------- */
/**
最大的可能的表容量。这个值必须正好是1<<30 1073741824,以保持在Java数组分配和索引边界内,以两个表大小的幂,而且还需要进一步使用,因为32位哈希字段的前两位用于控制目的。
*/
private static final int MAXIMUM_CAPACITY = 1 << 30;
/**
*默认初始化容量
*/
private static final int DEFAULT_CAPACITY = 16;
/**
最大的数组大小(非2的幂次)。
*/
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
jdk1.8只有在初始化的时候用到,不再表示并发级别了~ 1.8以后并发级别由散列表长度决定
*/
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
/**
负载因子 该属性是固定值0.75,不可修改
*/
private static final float LOAD_FACTOR = 0.75f;
/**
树化阈值:散列表的一个桶中链表长度达到8时候,可能发生链表树化
*/
static final int TREEIFY_THRESHOLD = 8;
/**
反树化阈值:散列表的一个桶中的红黑树元素个数小于6时候,将红黑树转换回链表结构
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
散列表长度达到64,且某个桶位中的链表长度达到8,才会发生树化
*/
static final int MIN_TREEIFY_CAPACITY = 64;
/**
控制线程迁移数据的最小步长(桶位的跨度~)
*/
private static final int MIN_TRANSFER_STRIDE = 16;
/**
固定值16,与扩容相关,计算扩容时会根据该属性值生成一个扩容标识戳
*/
private static int RESIZE_STAMP_BITS = 16;
/**
表示并发扩容最多容纳的线程数
*/
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
/**
* The bit shift for recording size stamp in sizeCtl.
*/
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
/*
* Encodings for Node hash fields. See above for explanation.
*/
static final int MOVED = -1; //当node节点的hash值为-1:表示当前节点是FWD(forwarding)节点(已经被迁移的节点)
static final int TREEBIN = -2; //当node节点的hash值为-2:表示当前节点已经树化,且当前节点为TreeBin对象~,TreeBin对象代理操作红黑树
static final int RESERVED = -3; // hash for transient reservations
// 0x7fffffff 十六进制转二进制值为:1111111111111111111111111111111(31个1)
// 作用是将一个二进制负数与1111111111111111111111111111111 进行按位与(&)运算时,会得到一个正数,但不是取绝对值
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
/** cpu的数量,对某些大小设置限制 */
static final int NCPU = Runtime.getRuntime().availableProcessors();
// JDK1.8 序列化为了兼容 JDK1.7的ConcurrentHashMap用到的属性 (非核心属性)
private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("segments", Segment[].class),
new ObjectStreamField("segmentMask", Integer.TYPE),
new ObjectStreamField("segmentShift", Integer.TYPE)
};
静态属性
// Unsafe mechanics
private static final sun.misc.Unsafe U;
//表示sizeCtl属性在ConcurrentHashMap中内存的偏移地址
private static final long SIZECTL;
// 表示transferIndex属性在ConcurrentHashMap中内存的偏移地址
private static final long TRANSFERINDEX;
// 表示baseCount属性在map中的内存偏移地址
private static final long BASECOUNT;
// cells属性的偏移地址
private static final long CELLSBUSY;
private static final long CELLVALUE;
// 数组中第一个元素偏移地址
private static final long ABASE;
// 数组寻址
private static final int ASHIFT;
静态代码
static {
try {
U = sun.misc.Unsafe.getUnsafe();
Class<?> k = ConcurrentHashMap.class;
SIZECTL = U.objectFieldOffset
(k.getDeclaredField("sizeCtl"));
TRANSFERINDEX = U.objectFieldOffset
(k.getDeclaredField("transferIndex"));
BASECOUNT = U.objectFieldOffset
(k.getDeclaredField("baseCount"));
CELLSBUSY = U.objectFieldOffset
(k.getDeclaredField("cellsBusy"));
Class<?> ck = CounterCell.class;
CELLVALUE = U.objectFieldOffset
(ck.getDeclaredField("value"));
Class<?> ak = Node[].class;
ABASE = U.arrayBaseOffset(ak);
// 表示数组中每一个单元所占用的空间大小,即scale表示Node[]数组中每一个单元所占用的空间
int scale = U.arrayIndexScale(ak);
if ((scale & (scale - 1)) != 0) // 判断scale数值是否是2的次幂数
throw new Error("data type scale not a power of two");
//根据scale,返回当前数值转换为二进制后,从高位到地位开始统计,统计有多少个0连续在一块:eg, 8转换二进制=>1000 则 numberOfLeadingZeros(8)的结果就是2
// 返回这个数据的二进制串中从最左边算起连续的“0”的总数量
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
} catch (Exception e) {
throw new Error(e);
}
}
// Unsafe类使用了单例模式,需要通过一个静态方法getUnsafe()来获取。但Unsafe类做了限制,如果是普通的调用的话,它会抛出一个SecurityException异常;只有由主类加载器加载的类才能调用这个方法
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
System.out.println(unsafe);
构造函数
ConcurrentHashMap在构造函数中只会初始化sizeCtl值,并不会直接初始化table,而是延缓到第一次put操作
/**
* Creates a new, empty map with the default initial table size (16).
*/
public ConcurrentHashMap() {
}
/**
* Creates a new, empty map with an initial table size
* accommodating the specified number of elements without the need
* to dynamically resize.
*
* @param initialCapacity The implementation performs internal
* sizing to accommodate this many elements.
* @throws IllegalArgumentException if the initial capacity of
* elements is negative
*/
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
/**
* Creates a new map with the same mappings as the given map.
*
* @param m the map
*/
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
this.sizeCtl = DEFAULT_CAPACITY;
putAll(m);
}
/**
* Creates a new, empty map with an initial table size based on
* the given number of elements ({@code initialCapacity}) and
* initial table density ({@code loadFactor}).
*
* @param initialCapacity the initial capacity. The implementation
* performs internal sizing to accommodate this many elements,
* given the specified load factor.
* @param loadFactor the load factor (table density) for
* establishing the initial table size
* @throws IllegalArgumentException if the initial capacity of
* elements is negative or the load factor is nonpositive
*
* @since 1.6
*/
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);
}
/**
* Creates a new, empty map with an initial table size based on
* the given number of elements ({@code initialCapacity}), table
* density ({@code loadFactor}), and number of concurrently
* updating threads ({@code concurrencyLevel}).
*
* @param initialCapacity the initial capacity. The implementation
* performs internal sizing to accommodate this many elements,
* given the specified load factor.
* @param loadFactor the load factor (table density) for
* establishing the initial table size
* @param concurrencyLevel the estimated number of concurrently
* updating threads. The implementation may use this value as
* a sizing hint.
* @throws IllegalArgumentException if the initial capacity is
* negative or the load factor or concurrencyLevel are
* nonpositive
*/
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (initialCapacity < concurrencyLevel) // Use at least as many bins
initialCapacity = concurrencyLevel; // as estimated threads
long size = (long)(1.0 + (long)initialCapacity / loadFactor);
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)size);
this.sizeCtl = cap;
}
添加元素
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
// key和value 不能为空
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
// 初始化表 如果为空,懒汉式
tab = initTable();
// 通过&运算计算这个key在table中的位置
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // 如果该位置没有元素,通过cas操作添加元素,此时没有上锁
}
//如果检测到hash值时MOVED,表示正在进行数组扩张的数据复制阶段
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f); //执行helpTransfer方法帮助复制,减少性能损失
else {
/* *如果这个位置有元素就进行加锁,
*如果是链表,就遍历所有元素,如果存在相同key,则覆盖value,否则将数据添加在尾部
*如果是红黑树,则调用putTreeVal的方式添加元素
*最后判断同一节点链表元素个数是否达到8个,达到就转链表为红黑树或扩容
*/
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD) //当一个节点中元素数量大于等于8的时候,执行treeifyBin
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
// 最终hash值计算
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
// 判断table是否初始化完成
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0) // 正在初始化
Thread.yield(); // 休眠
// 读取传入对象o在内存中偏移量为offset位置的值与期望值expected作比较。相等就把x值赋值给offset位置的值。方法返回true。不相等,就取消赋值,方法返回false。
// 通过cas操作去竞争初始化表的操作,设定为-1表示要初始化了
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
//如果指定了大小就创建指定大小的数组,否则创建默认的大小
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc; //sizeCtl长度为数组长度的3/4
}
break;
}
}
return tab;
}
- 根据 key 计算出 hashcode 。
- 判断是否需要进行初始化。
- 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
- 如果当前位置的
hashcode == MOVED == -1,则需要进行扩容。 - 如果都不满足,则利用 synchronized 锁写入数据。
- 如果数量大于
TREEIFY_THRESHOLD则要执行树化方法,在treeifyBin中会首先判断当前数组长度≥64时才会将链表转换为红黑树。
sizeCtl :默认为0,用来控制table的初始化和扩容操作
-1 代表table正在初始化
-N 表示取-N对应的二进制的低16位数值为M,此时有M-1个线程进行扩容
其余情况: 1、如果table未初始化,表示table需要初始化的大小。
2、如果table初始化完成,表示table的容量,默认是table大小的0.75倍
扩容操作
当一条链表中元素个数大于等于8时,会执行treeifyBin来判断是扩容还是转化为红黑树。
/*
*当table长度小于64的时候,扩张数组长度一倍,否则把链表转化为红黑树
*/
private final void treeifyBin(Node[] tab, int index) {
Node b; int n, sc;
if (tab != null) {
if ((n = tab.length) < MIN_TREEIFY_CAPACITY) //如果table长度小于64
tryPresize(n << 1); //table长度扩大一倍
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) { //否则,将链表转为树
synchronized (b) {
if (tabAt(tab, index) == b) {
TreeNode hd = null, tl = null;
for (Node e = b; e != null; e = e.next) {
TreeNode p =
new TreeNode(e.hash, e.key, e.val,
null, null);
if ((p.prev = tl) == null)
hd = p;
else
tl.next = p;
tl = p;
}
setTabAt(tab, index, new TreeBin(hd)); //把头节点放入容器TreeBin中
}
}
}
}
}
private final void tryPresize(int size) {
//计算c的大小,如果size比最大容量一半还大,则直接等于最大容量,否则通过tableSizeFor计算出一个2的幂次方的数
//计算出的这个c会与sizeCtl进行比较,一直到sizeCtl>=c时才会停止扩容
int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
tableSizeFor(size + (size >>> 1) + 1);
int sc;
//另sc等于sizeCtl
while ((sc = sizeCtl) >= 0) {
Node[] tab = table; int n;
//如果table为空则初始化,这里和初始化时代码一样
if (tab == null || (n = tab.length) == 0) {
n = (sc > c) ? sc : c;
if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if (table == tab) {
@SuppressWarnings("unchecked")
Node[] nt = (Node[])new Node[n];
table = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
}
}
//如果c比sizeCtl要小或者table的长度大于最大长度才停止扩容
else if (c <= sc || n >= MAXIMUM_CAPACITY)
break;
else if (tab == table) {
int rs = resizeStamp(n);
//如果正在扩容(sc<0),帮助扩容
if (sc < 0) {
Node[] nt;
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);
}
}
}
取出元素
get操作不设计线程安全,因此不用加锁。首先通过hash值判断该元素放在table的哪个位置,通过遍历的方式找到指定key的值,不存在返回null
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
按照key删除元素
public V remove(Object key) {
return replaceNode(key, null, null);
}
final V replaceNode(Object key, V value, Object cv) {
int hash = spread(key.hashCode());
for (Node[] tab = table;;) {
Node f; int n, i, fh;
//如果table为空或者发现不存在该key,直接退出循环
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
//如果等于MOVED,表示其他线程正在扩容,帮助扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
boolean validated = false;
synchronized (f) {
//二次校验,如果tabAt(tab, i)不等于f,说明已经被修改了
if (tabAt(tab, i) == f) {
if (fh >= 0) {
validated = true;
for (Node e = f, pred = null;;) {
K ek;
//找到对应的节点
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
V ev = e.val;
//删除节点或者更新节点的条件
if (cv == null || cv == ev ||
(ev != null && cv.equals(ev))) {
oldVal = ev;
//更新节点
if (value != null)
e.val = value;
//删除非头节点
else if (pred != null)
pred.next = e.next;
//删除头节点
else
setTabAt(tab, i, e.next);
}
break;
}
//继续遍历
pred = e;
if ((e = e.next) == null)
break;
}
}
//如果是红黑树则按照树的方式删除或更新节点
else if (f instanceof TreeBin) {
validated = true;
TreeBin t = (TreeBin)f;
TreeNode r, p;
if ((r = t.root) != null &&
(p = r.findTreeNode(hash, key, null)) != null) {
V pv = p.val;
if (cv == null || cv == pv ||
(pv != null && cv.equals(pv))) {
oldVal = pv;
if (value != null)
p.val = value;
else if (t.removeTreeNode(p))
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
if (validated) {
if (oldVal != null) {
//如果删除了节点,更新长度
if (value == null)
addCount(-1L, -1);
return oldVal;
}
break;
}
}
}
return null;
}
helpTransfer
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
if (tab != null && (f instanceof ForwardingNode) &&
// table不是空 且 node节点是转移类型,并且转移类型的nextTable 不是空 说明还在扩容ing
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
int rs = resizeStamp(tab.length);
// 确定新table指向没有变,老table数据也没变,并且此时 sizeCtl小于0 还在扩容ing
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
// 1. sizeCtl 无符号右移16位获得高16位如果不等 rs 标识符变了\
// 2. 如果扩容结束了 这里可以看 trePresize 函数第一次扩容操作:\
// 默认第一个线程设置 sc = rs 左移 16 位 + 2,当第一个线程结束扩容了,\
// 就会将 sc 减一。这个时候,sc 就等于 rs + 1。\
// 3. 如果达到了最大帮助线程个数 65535个\
// 4. 如果转移下标调整ing 扩容已经结束了
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
// 如果以上都不是, 将 sizeCtl + 1,增加一个线程来扩容
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
addCount
一是更新 baseCount,二是判断是否需要扩容
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// 首先如果没有并发 此时countCells is null, 此时尝试CAS设置数据值。
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
// 如果 counterCells不为空以为此时有并发的设置 或者 CAS设置 baseCount 失败了
CounterCell a; long v; int m;
boolean uncontended = true;
// ThreadLocalRandom 当线程发生数组元素争用后,可以改变线程的探针哈希值,让线程去使用另一个数组元素
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
// 1. 如果没出现并发 此时计数盒子为 null\
// 2. 随机取出一个数组位置发现为空\
// 3. 出现并发后修改这个cellvalue 失败了
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添加 ConcurrentHashMap提供了baseCount、counterCells 两个辅助变量和一个 CounterCell辅助内部类。sumCount() 就是迭代 counterCells来统计 sum 的过程。 put 操作时,肯定会影响 size(),在 put() 方法最后会调用 addCount()方法。
size
在 JDK1.7 中,第一种方案他会使用不加锁的模式去尝试多次计算 ConcurrentHashMap 的 size,最多三次,比较前后两次计算的结果,结果一致就认为当前没有元素加入,计算的结果是准确的。 第二种方案是如果第一种方案不符合,他就会给每个 Segment 加上锁,然后计算 ConcurrentHashMap 的 size 返回。 jdk1.8实现
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
但是一般推荐使用mappingCount
public long mappingCount() {
long n = sumCount();
return (n < 0L) ? 0L : n; // ignore transient negative values
}
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() 代码。ConcurrentHashMap 提供了 baseCount、counterCells 两个辅助变量和一个 CounterCell 辅助内部类。sumCount() 就是迭代 counterCells 来统计 sum 的过程。 put 操作时,肯定会影响 size(),在 put() 方法最后会调用 addCount() 方法。
CounterCell我们会发现它使用了 @sun.misc.Contended 标记的类,内部包含一个 volatile 变量。@sun.misc.Contended 这个注解标识着这个类防止需要防止 "伪共享"。那么,什么又是伪共享呢?
缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。
- JDK1.7 和 JDK1.8 对 size 的计算是不一样的。 1.7 中是先不加锁计算三次,如果三次结果不一样在加锁。
- JDK1.8 size 是通过对 baseCount 和 counterCell 进行 CAS 计算,最终通过 baseCount 和 遍历 CounterCell 数组得出 size。
- JDK 8 推荐使用mappingCount 方法,因为这个方法的返回值是 long 类型,不会因为 size 方法是 int 类型限制最大值。