1.针对size方法的讲解,上源码:
public int size() { long n = sumCount(); return ((n < 0L) ? 0 : (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)n); }
可以看到源码的size方法,是调用了sumCount方法:
/** * 设计巧妙的地方就在于,如果只有一个baseCount来储存map的size大小, * 那么多线程竞争时,势必需要对该变量进行频繁的读写操作,如果直接加 * 锁的话那并发度肯定直接下降,所以只能大师Doug Lea使用了一个CounterCell * 的内部类的数组:当每个putValue方法去addCount的时候,如果此时存在竞争关系 * 那么线程间会通过一个随机数的hash算法定位到CounterCell[]中某个位置,并将自己 * 添加的元素个数写进去,采用这种方式的设计就充分的将原来从一个baseCount的并发度 * 直接变成了一个数组大小的并发度,这里就是设计巧妙的地方 */ final long sumCount() { //用于并发计算设计的内部类 CounterCell[] cs = counterCells; //volatile型的变量,当程序没有发生竞争时,代表的就是map的size。当发生竞争时,作为 //一个基础值然后在累加CounterCell数组中的值 long sum = baseCount; if (cs != null) { for (CounterCell c : cs) if (c != null) sum += c.value; } return sum; }
CounterCell内部类如图:
这里使用了一个Contended的注解,其实就是使用的传说中的64字节填充术,防止cpu cache被
其他变量更改被刷新从而又从主内存读取数据引发的性能降低,因为这里是需要一个cell的数组,
发生竞争时需要分别对不同数组下标的元素进行读写
很多朋友会跟我一样可能搜索很多资料,觉得看到这里就估计结束了,但是本着探索精神凡是要多
问自己几个为什么,怎么实现的呢?那么就接着上述话题,我们直接进入putVal方法调用的
addCount方法具体是怎么玩的:
final V putVal(K key, V value, boolean onlyIfAbsent) { 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; K fk; V fv; if (tab == null || (n = tab.length) == 0) tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value))) break; // no lock when adding to empty bin } else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); //省略中间添加元素的源码部分 } addCount(1L, binCount); return null; }
/**
* Adds to count, and if table is too small and not already
* resizing, initiates transfer. If already resizing, helps
* perform transfer if work is available. Rechecks occupancy
* after a transfer to see if another resize is already needed
* because resizings are lagging additions.
* 看源码中binCount其实就是代表发生hash冲突链上的元素大小
* 这里特意说明一下
* 注释翻译过来就是,添加元素,如果table太小并且已经在resizing/初始化
* transfer,或者如果已经在resizing尽可能的帮助其进行transfer操作
* (这里的尽可能其实就是在最大的一个帮助线程数下进行的65535),因为
* 添加操作是滞后的,所以重新检查容量需要在transfer之后是为了判断是否
* 需要进行下一次的resize
* @param x the count to add
* @param check if <0, don't check resize, if <= 1 only check if uncontended
*/
采取化繁为简的方式,我们将这个方法先分成两部分看第一部分做了什么:
调用fullAddCount方法的条件:
1.CounterCells存在,并且cell中不存在元素或者存在元素但是cas该cell的value失败
2.CounterCells不存在,并且cas baseCount失败
接着我们继续一探究竟fullAddCount方法:
//因为一张图片截图不了,所以这里直接拿源码来开刷了
先梳理一下方法主体内容是在干什么:
a.cell数组存在,如果当前根据hash定位的slot中不存在cell元素,那么创建一个cell并赋值返回方法,但是创建时需要一个cellsbusy的变量该变量用于cell数组resize的flag,值为0时时正常状态,为1时证明有线程在进行初始化或者扩容操作,如果slot中存在cell元素,并且cas该cell的value成功则返回,cas失败则表示此时该slot存在大量竞争,接下来会进行一个cell数组的扩容操作
b.当cell数组不存在时,cas cellsbusy的flag变量,然后进行counterCell的数组初始化,初始大小为2,初始化的同时直接通过hash定位slot将value值设置到定位的cell中,直接返回
c.当a、b都没执行成功,那么此时就会直接进行baseCount变量的cas的兜底操作
private final void fullAddCount(long x, boolean wasUncontended) { int h; if ((h = ThreadLocalRandom.getProbe()) == 0) { ThreadLocalRandom.localInit(); // force initialization h = ThreadLocalRandom.getProbe(); wasUncontended = true; } boolean collide = false; // True if last slot nonempty for (;;) { CounterCell[] cs; CounterCell c; int n; long v; if ((cs = counterCells) != null && (n = cs.length) > 0) { if ((c = cs[(n - 1) & h]) == null) { if (cellsBusy == 0) { // Try to attach new Cell CounterCell r = new CounterCell(x); // Optimistic create if (cellsBusy == 0 && U.compareAndSetInt(this, CELLSBUSY, 0, 1)) { boolean created = false; try { // Recheck under lock CounterCell[] rs; int m, j; if ((rs = counterCells) != null && (m = rs.length) > 0 && rs[j = (m - 1) & h] == null) { rs[j] = r; created = true; } } finally { cellsBusy = 0; } if (created) break; continue; // Slot is now non-empty } } collide = false; } else if (!wasUncontended) // CAS already known to fail wasUncontended = true; // Continue after rehash else if (U.compareAndSetLong(c, CELLVALUE, v = c.value, v + x)) break; else if (counterCells != cs || n >= NCPU) collide = false; // At max size or stale else if (!collide) collide = true; else if (cellsBusy == 0 && U.compareAndSetInt(this, CELLSBUSY, 0, 1)) { try { if (counterCells == cs) // Expand table unless stale counterCells = Arrays.copyOf(cs, n << 1); } finally { cellsBusy = 0; } collide = false; continue; // Retry with expanded table } h = ThreadLocalRandom.advanceProbe(h); } else if (cellsBusy == 0 && counterCells == cs && U.compareAndSetInt(this, CELLSBUSY, 0, 1)) { boolean init = false; try { // Initialize table if (counterCells == cs) { CounterCell[] rs = new CounterCell[2]; rs[h & 1] = new CounterCell(x); counterCells = rs; init = true; } } finally { cellsBusy = 0; } if (init) break; } else if (U.compareAndSetLong(this, BASECOUNT, v = baseCount, v + x)) break; // Fall back on using base } }