和HashMap类似,ConcurrentHashMap使用了一个table来存储Node,ConcurrentHashMap同样使用记录的key的hashCode来寻找记录的存储index,而处理哈希冲突的方式与HashMap也是类似的,冲突的记录将被存储在同一个位置上,形成一条链表,当链表的长度大于8的时候会将链表转化为一棵红黑树,从而将查找的复杂度从O(N)降到了O(lgN)。下文中将详细分析ConcurrentHashMap的实现,以及ConcurrentHashMap是如何保证在并发环境下的线程安全的。
1、重要的属性
sizeCtl,是使用的最多的一个属性,
- 负数代表正在进行初始化或扩容操作,
- -1代表正在初始化,
- -N表示有N-1个线程在进行扩容操作
正数或0代表hash表还没被初始化,表示初始化或下次扩容的大小,起到阈值的概念 ```java
/**
- 盛装Node元素的数组 它的大小是2的整数次幂
Size is always a power of two. Accessed directly by iterators. */ transient volatile Node
[] table; /**
- Table initialization and resizing control. When negative, the
- table is being initialized or resized: -1 for initialization,
- else -(1 + the number of active resizing threads). Otherwise,
- when table is null, holds the initial table size to use upon
- creation, or 0 for default. After initialization, holds the
next element count value upon which to resize the table. hash表初始化或扩容时的一个控制位标识量。 负数代表正在进行初始化或扩容操作 -1代表正在初始化 -N 表示有N-1个线程正在进行扩容操作 正数或0代表hash表还没有被初始化,这个数值表示初始化或下一次进行扩容的大小
/ private transient volatile int sizeCtl; // 以下两个是用来控制扩容的时候 单线程进入的变量 /*
- The number of bits used for generation stamp in sizeCtl.
- Must be at least 6 for 32bit arrays. / private static int RESIZE_STAMP_BITS = 16; /*
- 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; // hash值是-1,表示这是一个forwardNode节点static final int TREEBIN = -2; // hash值是-2 表示这时一个TreeBin节点
<a name="sk99ku"></a># 2、重要的内部类<a name="vni5xu"></a>## 2.1、nodenode是最核心的内部类,包装了key-value键值对,所有插入的值都存在了这里,但是value和next用了volatile修饰,保证线程可见,不允许调用setValue方法,提供了find方法```javastatic class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;volatile V val;//带有同步锁的valuevolatile Node<K,V> next;//带有同步锁的next指针Node(int hash, K key, V val, Node<K,V> next) {this.hash = hash;this.key = key;this.val = val;this.next = next;}public final K getKey() { return key; }public final V getValue() { return val; }public final int hashCode() { return key.hashCode() ^ val.hashCode(); }public final String toString(){ return key + "=" + val; }//不允许直接改变value的值public final V setValue(V value) {throw new UnsupportedOperationException();}public final boolean equals(Object o) {Object k, v, u; Map.Entry<?,?> e;return ((o instanceof Map.Entry) &&(k = (e = (Map.Entry<?,?>)o).getKey()) != null &&(v = e.getValue()) != null &&(k == key || k.equals(key)) &&(v == (u = val) || v.equals(u)));}/*** Virtualized support for map.get(); overridden in subclasses.*/Node<K,V> find(int h, Object k) {Node<K,V> e = this;if (k != null) {do {K ek;if (e.hash == h &&((ek = e.key) == k || (ek != null && k.equals(ek))))return e;} while ((e = e.next) != null);}return null;}}
2.2、TreeNode
树节点类,当链表长度过长的时候,会转换成TreeNode,于HashMap不同的是,它并不是直接转换为红黑树,而是把这些节点包装成TreeNode放在TreeBin对象中,由TreeBin完成对红黑树的包装,TreeNode带有next指针,方便基于TreeBin的访问。
/* ---------------- TreeNodes -------------- *//*** Nodes for use in TreeBins*/static final class TreeNode<K,V> extends Node<K,V> {TreeNode<K,V> parent; // red-black tree linksTreeNode<K,V> left;TreeNode<K,V> right;TreeNode<K,V> prev; // needed to unlink next upon deletionboolean red;TreeNode(int hash, K key, V val, Node<K,V> next,TreeNode<K,V> parent) {super(hash, key, val, next);this.parent = parent;}Node<K,V> find(int h, Object k) {return findTreeNode(h, k, null);}/*** Returns the TreeNode (or null if not found) for the given key* starting at given root.*/final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {if (k != null) {TreeNode<K,V> p = this;do {int ph, dir; K pk; TreeNode<K,V> q;TreeNode<K,V> pl = p.left, pr = p.right;if ((ph = p.hash) > h)p = pl;else if (ph < h)p = pr;else if ((pk = p.key) == k || (pk != null && k.equals(pk)))return p;else if (pl == null)p = pr;else if (pr == null)p = pl;else if ((kc != null ||(kc = comparableClassFor(k)) != null) &&(dir = compareComparables(kc, k, pk)) != 0)p = (dir < 0) ? pl : pr;else if ((q = pr.findTreeNode(h, k, kc)) != null)return q;elsep = pl;} while (p != null);}return null;}}
2.3、TreeBin
这个类包装了TreeNode节点来进行操作,还带有读写锁,构造方法中使用了TREEBIN常量作为hash值,标识节点类型
2.4、ForwardingNode
一个用于连接两个table的节点类,包含一个nextTable指针,指向下一张表,而这个节点的key value next指针全为null,hash值为-1,这里的find方法是从nextTable里进行查找,而不是以自身为头结点进行查找。
/*** A node inserted at head of bins during transfer operations.*/static final class ForwardingNode<K,V> extends Node<K,V> {final Node<K,V>[] nextTable;ForwardingNode(Node<K,V>[] tab) {super(MOVED, null, null, null);this.nextTable = tab;}Node<K,V> find(int h, Object k) {// loop to avoid arbitrarily deep recursion on forwarding nodesouter: for (Node<K,V>[] tab = nextTable;;) {Node<K,V> e; int n;if (k == null || tab == null || (n = tab.length) == 0 ||(e = tabAt(tab, (n - 1) & h)) == null)return null;for (;;) {int eh; K ek;if ((eh = e.hash) == h &&((ek = e.key) == k || (ek != null && k.equals(ek))))return e;if (eh < 0) {if (e instanceof ForwardingNode) {tab = ((ForwardingNode<K,V>)e).nextTable;continue outer;}elsereturn e.find(h, k);}if ((e = e.next) == null)return null;}}}}
3、Unsafe于CAS
内部随处可见U 也就是Unsafe,是一个本地类,实现了一系列的原子方法,利用CAS算法实现无锁化的修改操作,大大降低锁的性能消耗,CAS就是不断去比较当前值是否是你指定的值,如果是就接受并修改成功,如果不是修改失败,返回false
3.1、unsafe静态块
private static final sun.misc.Unsafe U;private static final long SIZECTL;private static final long TRANSFERINDEX;private static final long BASECOUNT;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);int scale = U.arrayIndexScale(ak);if ((scale & (scale - 1)) != 0)throw new Error("data type scale not a power of two");ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);} catch (Exception e) {throw new Error(e);}}
3.2、三个核心方法
定义了三个原子操作,用于对指定位置节点进行操作
@SuppressWarnings("unchecked")//获得在i位置上的Node节点static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);}//利用CAS算法设置i位置上的Node节点。之所以能实现并发是因为他指定了原来这个节点的值是多少//在CAS算法中,会比较内存中的值与你指定的这个值是否相等,如果相等才接受你的修改,否则拒绝你的修改//因此当前线程中的值并不是最新的值,这种修改可能会覆盖掉其他线程的修改结果 有点类似于SVNstatic final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,Node<K,V> c, Node<K,V> v) {return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);}//利用volatile方法设置节点位置的值static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);}
4、初始化方法initTable
对应ConcurrentHashMap来说,调用构造方法只是设置了一些参数,真正初始化是在插入元素的时候发生的,比如put,computeIfAbsent,compute,merge等方法的时候,通过table == null判断,使用sizeCtl属性判断,如果值小于0,表示正在初始化,交出CPU,只能由一个线程进行初始化,如果获得了初始化权限,通过CAS将sizeCtl置为-1获得,防止其他线程进入
/*** Initializes table, using the size recorded in sizeCtl.*/private final Node<K,V>[] initTable() {Node<K,V>[] tab; int sc;while ((tab = table) == null || tab.length == 0) {//sizeCtl表示有其他线程正在进行初始化操作,把线程挂起。对于table的初始化工作,只能有一个线程在进行。if ((sc = sizeCtl) < 0)Thread.yield(); // lost initialization race; just spinelse if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {//利用CAS方法把sizectl的值置为-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);//相当于0.75*n 设置一个扩容的阈值}} finally {sizeCtl = sc;}break;}}return tab;}
5、扩容方法transfer
容量不足的时候,需要对table进行扩容,整个扩容分为两部分
- 构建nextTable,容量为原来的两倍,单线程进行操作,通过RESIZE_STAMP_SHIFT变量来保证
- 将原来table中的元素复制到nextTable中,允许多线程操作单线程大体思想就是遍历、复制的过程,首先根据运算得到要遍历的次数i,然后利用tabAt方法获得i位置的元素:
- 如果这个位置为空,就在原table中的i位置放入forwardNode节点,这里是支持并发扩容的关键
- 如果这个位置是Node节点(fn>=0),如果是链表的头结点,就构造一个反序链表,把他们分别放在nextTable的i和i+n的位置
- 如果是TreeBin节点,也做一个反序处理并判断是否需要untreeify,并把处理结果分别放在nextTable的i和i+1的位置上
遍历过所有的节点以后就可以完成复制工作,这时让nextTable作为新的table,并更新sizeCtl的值为原来的0.75倍
/*** 一个过渡的table表 只有在扩容的时候才会使用*/private transient volatile Node<K,V>[] nextTable;/*** Moves and/or copies the nodes in each bin to new table. See* above for explanation.*/private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {int n = tab.length, stride;if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)stride = MIN_TRANSFER_STRIDE; // subdivide rangeif (nextTab == null) { // initiatingtry {@SuppressWarnings("unchecked")//构造一个nextTable对象 它的容量是原来的两倍Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];nextTab = nt;} catch (Throwable ex) { // try to cope with OOMEsizeCtl = Integer.MAX_VALUE;return;}nextTable = nextTab;transferIndex = n;}int nextn = nextTab.length;//构造一个连节点指针 用于标志位ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);//并发扩容的关键属性 如果等于true 说明这个节点已经处理过boolean advance = true;boolean finishing = false;// to ensure sweep before committing nextTabfor (int i = 0, bound = 0;;) {Node<K,V> f; int fh;//这个while循环体的作用就是在控制i-- 通过i--可以依次遍历原hash表中的节点while (advance) {int nextIndex, nextBound;if (--i >= bound || finishing)advance = false;else if ((nextIndex = transferIndex) <= 0) {i = -1;advance = false;}else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex,nextBound = (nextIndex > stride ?nextIndex - stride : 0))) {bound = nextBound;i = nextIndex - 1;advance = false;}}if (i < 0 || i >= n || i + n >= nextn) {int sc;if (finishing) {//如果所有的节点都已经完成复制工作 就把nextTable赋值给table 清空临时对象nextTablenextTable = null;table = nextTab;//扩容阈值设置为原来容量的1.5倍 依然相当于现在容量的0.75倍sizeCtl = (n << 1) - (n >>> 1);return;}//利用CAS方法更新这个扩容阈值,在这里面sizectl值减一,说明新加入一个线程参与到扩容操作if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)return;finishing = advance = true;i = n; // recheck before commit}}//如果遍历到的节点为空 则放入ForwardingNode指针else if ((f = tabAt(tab, i)) == null)advance = casTabAt(tab, i, null, fwd);//如果遍历到ForwardingNode节点 说明这个点已经被处理过了 直接跳过 这里是控制并发扩容的核心else if ((fh = f.hash) == MOVED)advance = true; // already processedelse {//节点上锁synchronized (f) {if (tabAt(tab, i) == f) {Node<K,V> ln, hn;//如果fh>=0 证明这是一个Node节点if (fh >= 0) {int runBit = fh & n;//以下的部分在完成的工作是构造两个链表 一个是原链表 另一个是原链表的反序排列Node<K,V> lastRun = f;for (Node<K,V> p = f.next; p != null; p = p.next) {int b = p.hash & n;if (b != runBit) {runBit = b;lastRun = p;}}if (runBit == 0) {ln = lastRun;hn = null;}else {hn = lastRun;ln = null;}for (Node<K,V> p = f; p != lastRun; p = p.next) {int ph = p.hash; K pk = p.key; V pv = p.val;if ((ph & n) == 0)ln = new Node<K,V>(ph, pk, pv, ln);elsehn = new Node<K,V>(ph, pk, pv, hn);}//在nextTable的i位置上插入一个链表setTabAt(nextTab, i, ln);//在nextTable的i+n的位置上插入另一个链表setTabAt(nextTab, i + n, hn);//在table的i位置上插入forwardNode节点 表示已经处理过该节点setTabAt(tab, i, fwd);//设置advance为true 返回到上面的while循环中 就可以执行i--操作advance = true;}//对TreeBin对象进行处理 与上面的过程类似else if (f instanceof TreeBin) {TreeBin<K,V> t = (TreeBin<K,V>)f;TreeNode<K,V> lo = null, loTail = null;TreeNode<K,V> hi = null, hiTail = null;int lc = 0, hc = 0;//构造正序和反序两个链表for (Node<K,V> e = t.first; e != null; e = e.next) {int h = e.hash;TreeNode<K,V> p = new TreeNode<K,V>(h, e.key, e.val, null, null);if ((h & n) == 0) {if ((p.prev = loTail) == null)lo = p;elseloTail.next = p;loTail = p;++lc;}else {if ((p.prev = hiTail) == null)hi = p;elsehiTail.next = p;hiTail = p;++hc;}}//如果扩容后已经不再需要tree的结构 反向转换为链表结构ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :(hc != 0) ? new TreeBin<K,V>(lo) : t;hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :(lc != 0) ? new TreeBin<K,V>(hi) : t;//在nextTable的i位置上插入一个链表setTabAt(nextTab, i, ln);//在nextTable的i+n的位置上插入另一个链表setTabAt(nextTab, i + n, hn);//在table的i位置上插入forwardNode节点 表示已经处理过该节点setTabAt(tab, i, fwd);//设置advance为true 返回到上面的while循环中 就可以执行i--操作advance = true;}}}}}}
6、put 方法
根据hash值计算新值的位置i,如果i位置为空,就直接放进去,否则进行判断,是树节点就按照树的方式插入新值,如果是链表就插入到链表的尾部
- 如果有线程在进行扩容操作,当前线程也要进入扩容操作
- 如果检测到要插入的节点非空且不是forward节点,就加锁进行添加操作。
如果这个位置存在结点,说明发生了hash碰撞,首先判断这个节点的类型。如果是链表节点(fh>0),则得到的结点就是hash值相同的节点组成的链表的头节点。需要依次向后遍历确定这个新加入的值所在位置。如果遇到hash值与key值都与新加入节点是一致的情况,则只需要更新value值即可。否则依次向后遍历,直到链表尾插入这个结点。 如果加入这个节点以后链表长度大于8,就把这个链表转换成红黑树。如果这个节点的类型已经是树节点的话,直接调用树节点的插入方法进行插入新的值。
public V put(K key, V value) {return putVal(key, value, false);}/** Implementation for put and putIfAbsent */final V putVal(K key, V value, boolean onlyIfAbsent) {//不允许 key或value为nullif (key == null || value == null) throw new NullPointerException();//计算hash值int hash = spread(key.hashCode());int binCount = 0;//死循环 何时插入成功 何时跳出for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;//如果table为空的话,初始化tableif (tab == null || (n = tab.length) == 0)tab = initTable();//根据hash值计算出在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; // no lock when adding to empty bin}//当遇到表连接点时,需要进行整合表的操作else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);else {V oldVal = null;//结点上锁 这里的结点可以理解为hash值相同组成的链表的头结点synchronized (f) {if (tabAt(tab, i) == f) {//fh〉0 说明这个节点是一个链表的节点 不是树的节点if (fh >= 0) {binCount = 1;//在这里遍历链表所有的结点for (Node<K,V> e = f;; ++binCount) {K ek;//如果hash值和key值相同 则修改对应结点的value值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) {//如果链表长度已经达到临界值8 就需要把链表转换为树结构if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal != null)return oldVal;break;}}}//将当前ConcurrentHashMap的元素数量+1addCount(1L, binCount);return null;}
6.1、helpTransfer方法
这是一个协助扩容的方法,被调用的时候一定有nextTable,首先拿到nextTable,调用transfer方法
/*** Helps transfer if a resize is in progress.*/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) &&(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {int rs = resizeStamp(tab.length);//计算一个操作校验码while (nextTab == nextTable && table == tab &&(sc = sizeCtl) < 0) {if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||sc == rs + MAX_RESIZERS || transferIndex <= 0)break;if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {transfer(tab, nextTab);break;}}return nextTab;}return table;}
6.2、treeifyBin方法
这个方法是将过长的链表转换为TreeBin对象,不是直接转换,先进行容量判断,没达到就进行扩容操作,满足条件后才将链表转换为TreeBin
private final void treeifyBin(Node<K,V>[] tab, int index) {Node<K,V> b; int n, sc;if (tab != null) {if ((n = tab.length) < MIN_TREEIFY_CAPACITY)//如果table.length<64 就扩大一倍 返回tryPresize(n << 1);else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {synchronized (b) {if (tabAt(tab, index) == b) {TreeNode<K,V> hd = null, tl = null;//构造了一个TreeBin对象 把所有Node节点包装成TreeNode放进去for (Node<K,V> e = b; e != null; e = e.next) {TreeNode<K,V> p =new TreeNode<K,V>(e.hash, e.key, e.val,null, null);//这里只是利用了TreeNode封装 而没有利用TreeNode的next域和parent域if ((p.prev = tl) == null)hd = p;elsetl.next = p;tl = p;}//在原来index的位置 用TreeBin替换掉原来的Node对象setTabAt(tab, index, new TreeBin<K,V>(hd));}}}}}
7、get方法
get方法比较简单,给定一个key确定value的时候,必须满足hash和key都相同,查找可能是链表或者树上。
public V get(Object key) {Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;//计算hash值int h = spread(key.hashCode());//根据hash值确定节点位置if ((tab = table) != null && (n = tab.length) > 0 &&(e = tabAt(tab, (n - 1) & h)) != null) {//如果搜索到的节点key与传入的key相同且不为null,直接返回这个节点if ((eh = e.hash) == h) {if ((ek = e.key) == key || (ek != null && key.equals(ek)))return e.val;}//如果eh<0 说明这个节点在树上 直接寻找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;}
8、size相关方法
8.1、辅助定义
为了统计元素个数,定义了一些变量和一个内部类
/*** 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; }}/******************************************//*** 实际上保存的是hashmap中的元素个数 利用CAS锁进行更新但它并不用返回当前hashmap的元素个数*/private transient volatile long baseCount;/*** Spinlock (locked via CAS) used when resizing and/or creating CounterCells.*/private transient volatile int cellsBusy;/*** Table of counter cells. When non-null, size is a power of 2.*/private transient volatile CounterCell[] counterCells;
8.2、mappingCount与size方法
从代码来看,mappingCount方法比size方法更准确,都用到了sumCount方法进行统计,由于没有进行加锁,可能会因为并发问题出现一些误差,因为size方法返回的是int类型,所以如果数量超过int最大值的情况下,还是使用mappingCount方法。
public int size() {long n = sumCount();return ((n < 0L) ? 0 :(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :(int)n);}/*** Returns the number of mappings. This method should be used* instead of {@link #size} because a ConcurrentHashMap may* contain more mappings than can be represented as an int. The* value returned is an estimate; the actual count may differ if* there are concurrent insertions or removals.** @return the number of mappings* @since 1.8*/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;//所有counter的值求和}}return sum;}
8.3、addCount方法
put方法最后调用了这个方法,将当前元素个数+1,一共做了两件事,更新baseCount的值,检查是否需要扩容。
private final void addCount(long x, int check) {CounterCell[] as; long b, s;//利用CAS方法更新baseCount的值if ((as = counterCells) != null ||!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 ||!(uncontended =U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {fullAddCount(x, uncontended);return;}if (check <= 1)return;s = sumCount();}//如果check值大于等于0 则需要检验是否需要进行扩容操作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);}//当前线程是唯一的或是第一个发起扩容的线程 此时nextTable=nullelse if (U.compareAndSwapInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2))transfer(tab, null);s = sumCount();}}}
参考资料
https://juejin.im/entry/59fc786d518825297f3fa968
https://blog.csdn.net/u010723709/article/details/48007881
