二 ConcurrentHashMap源码深度解析(java8)——直呼Dou( 二 )

[] nt = (Node[])new Node[n];table = tab = nt;// 3/4sc = n - (n >>> 2);}} finally {// 初始化完成,sizeCtl设置成当前容量的3/4,即为扩容阈值// 这里也相当于释放锁 。sizeCtl = sc;}break;}}return tab;}
(3)数组不为空了,则哈希映射数组下标(f = tabAt(tab, i = (n - 1) & hash)),从主内存中获取最新的节点,若为空则说明没有发生哈希冲突,cas设置新节点到对应位置,设置失败可能因为有其他线程竞争设置了,则重新循环判断 。(long)i = 0,判断其是普通节点,即链表,遍历链表,有key相同的节点则判断是替换还是直接结束,若是新增节点,则以尾插法加到链表尾部,在遍历链表的过程中自增,记录链表的长度,后面看是否需要转为红黑树 。(java7新增节点用的是头插法,用的也是头插法,并发情况下容易造成循环链表死循环,后来java8就都用尾插法了) 。
(7)fh < 0 && f判断是红黑树,返回值为null是新增节点,不为null则返回值是树中已存在的节点,判断是否需要替换 。
(8)树化判断,开始赋值为0,若新节点加到了链表中,会在遍历链表的过程中累加记录链表的长度,若新节点加到了红黑树中,赋值为2;>= ,说明链表的节点达到树化的阈值8个,则执行 。
达到树化阈值不一定就链表转为红黑树,若数组的长度小于=64,需要先扩容(n ==64,则锁住当前位置占位节点,开始树化 。
private final void treeifyBin(Node[] tab, int index) {Node b; int n, sc;if (tab != null) {if ((n = tab.length) < MIN_TREEIFY_CAPACITY)// tab 容量小于64,为什么要 << 1tryPresize(n << 1);else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {// 链表转为红黑树 。// 锁住的是一个节点synchronized (b) {// 再次判断tab index位置的节点是否有改变if (tabAt(tab, index) == b) {// hd 头、tl尾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;elsetl.next = p;// 尾指针指向最后一个节点tl = p;}// 红黑树化TreeBinsetTabAt(tab, index, new TreeBin(hd));}}}}}
(9)不是替换元素,需要最后执行(1L, ),元素个数+1 。
三、元素计数
较为复杂单独拎出来讨论 。一个简单的元素个数加减,如果让你来实现这个功能该如何做,一个修饰的变量,然后cas加减?在竞争激烈的情况,cas自旋可能会成为性能瓶颈,某些线程会因为cas计数失败而长时间自旋 。

二  ConcurrentHashMap源码深度解析(java8)——直呼Dou

文章插图
Doug Lea是怎么做的呢?基础变量()+数组([])辅助计数 。
作者的思路就是尽量避免竞争,cas修改成功就不会再去修改[],修改失败也不会自旋,以哈希映射的方式找到[]对应位置的格子cas计数,依然失败就多次重复哈希映射找其他空闲格子,还失败就扩容[],扩容之后都竞争不过其他线程,此时就进入自旋重复哈希映射,直到修改成功,虽然最终可能也会陷入不断自旋重试的情况,但是多个线程抢多个资源和多个线程抢一个资源相比,性能明显会好很多 。详情看代码:
private final void addCount(long x, int check) {CounterCell[] as; long b, s;// counterCells!=null,则直接在counterCells里加元素个数,// counterCells=null,则尝试修改baseCount,失败则修改counterCells 。if ((as = counterCells) != null ||!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {CounterCell a; long v; int m;// 初始化乐观的认为true即没有竞争boolean uncontended = true;// ThreadLocalRandom.getProbe() 相当于当前线程的hash值if (as == null || (m = as.length - 1)