概述

ConcurrentHashMap 是 util.concurrent 包的重要成员。

ConcurrentHashMap 的源代码会涉及到散列算法,链表数据结构和红黑树

Java8 ConcurrentHashMap 源码真心不简单,最难的在于扩容,数据迁移操作不容易看懂。

前面我画了几张图, 便于理解.  剩下的需要你自己静下新来,安心看源码.

建议从 put 方法开始着手看.

数据结构图

实体:

这里面最麻烦的是扩容方法 transfer(), 我画个两个图,方便理解.

具体代码,拆分. 这样会好看一点.

源码注释 :

为了方便 debug 我把ConcurrentHashMap 的名字变更为 ConcurrentHashMapSource

ThreadLocalRandom 这个类 正常访问不到,复制粘贴到包里就好了

put 方法:

/*** Maps the specified key to the specified value in this table.* Neither the key nor the value can be null.** <p>The value can be retrieved by calling the {@code get} method* with a key that is equal to the original key.** @param key key with which the specified value is to be associated* @param value value to be associated with the specified key* @return the previous value associated with {@code key}, or*         {@code null} if there was no mapping for {@code key}* @throws NullPointerException if the specified key or value is null*/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) {if (key == null || value == null) throw new NullPointerException();//todo  得到 hash 值int hash = spread(key.hashCode());//todo  用于记录相应链表的长度int binCount = 0;//todo 自旋  (  ;; )for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;//todo  如果数组"空",进行数组初始化if (tab == null || (n = tab.length) == 0)//todo  锁?tab = initTable();else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//todo  锁?//todo  找该 hash 值对应的数组下标,得到第一个节点 f//todo  如果数组该位置为空,//todo     用一次 CAS 操作将这个新值放入其中即可,这个 put 操作差不多就结束了,可以拉到最后面了//todo           如果 CAS 失败,那就是有并发操作,进到下一个循环就好了if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))//todo 添加完成, 跳出break;}//todo  hash  等于 MOVED  [ -1 ] ,else if ((fh = f.hash) == MOVED)//todo  帮助数据迁移,这个等到看完数据迁移部分的介绍后,再理解这个就很简单了tab = helpTransfer(tab, f);else {//todo  到这里就是说,f 是该位置的头结点,而且不为空V oldVal = null;//todo  锁?  锁对象//todo  获取数组该位置的头结点的监视器锁synchronized (f) {if (tabAt(tab, i) == f) {if (fh >= 0) {//todo  头结点的 hash 值大于 0,说明是链表//todo  用于累加,记录链表的长度binCount = 1;//todo  遍历链表for (Node<K,V> e = f;; ++binCount) {K ek;//todo  如果发现了"相等"的 key,判断是否要进行值覆盖,然后也就可以 break 了if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {oldVal = e.val;//todo 是否替换 默认替换  ,   onlyIfAbsent : falseif (!onlyIfAbsent)e.val = value;break;}//todo  到了链表的最末端,将这个新值放到链表的最后面Node<K,V> pred = e;if ((e = e.next) == null) {pred.next = new Node<K,V>(hash, key,value, null);break;}}}//todo  红黑树else if (f instanceof TreeBin) {Node<K,V> p;binCount = 2;//todo  调用红黑树的插值方法插入新节点if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value;}}}}//todo  binCount != 0 说明上面在做链表操作if (binCount != 0) {//todo  判断是否要将链表转换为红黑树,临界值和 HashMap 一样,也是 8if (binCount >= TREEIFY_THRESHOLD)//todo  这个方法和 HashMap 中稍微有一点点不同,那就是它不是一定会进行红黑树转换,//todo  如果当前数组的长度小于 64,那么会选择进行数组扩容,而不是转换为红黑树treeifyBin(tab, i);if (oldVal != null)return oldVal;break;}}}//todo 添加记录个数addCount(1L, binCount);return null;}

初始化数组:  tab = initTable();

/*** 使用sizeCtl中记录的大小初始化表。* 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) {//todo  初始化的"功劳"被其他线程"抢去"了if ((sc = sizeCtl) < 0)//todo 让出 cpuThread.yield(); // lost initialization race; just spin// todo  知识// todo CAS(Compare and Swap),即比较并替换  乐观锁// todo CAS的思想很简单:三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。// todo CAS 缺点  ABA// todo  CAS 一下,将 sizeCtl 设置为 -1,代表抢到了锁// todo  若 SIZECTL, sc 相等 , 则更改为 -1     :   0 未初始化,  -1 代表正在初始化//todo  compareAndSwapInt(java.lang.Object o, long l, int i, int i1)// 比较对象 o 中偏移量 l 中的值是否为 i , 是的话将值更改为   i1 (新值)else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {try {if ((tab = table) == null || tab.length == 0) {//todo  DEFAULT_CAPACITY 默认初始容量是 16int n = (sc > 0) ? sc : DEFAULT_CAPACITY;@SuppressWarnings("unchecked")//todo  初始化数组,长度为 16 或初始化时提供的长度Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];//todo  将这个数组赋值给 table,table 是 volatile 的table = tab = nt;//todo  如果 n 为 16 的话,那么这里 sc = 12//todo  其实就是 0.75 * nsc = n - (n >>> 2);}} finally {//todo  设置 sizeCtl 为 sc,  12 吧sizeCtl = sc;}break;}}return tab;}

当头结点的 hash 值为 REMOVE ( -1 )  帮助扩容:

 /*** 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;}

所有的扩容方法都调用: transfer(Node<K,V>[] tab, Node<K,V>[] nextTab)   方法.

这个方法不是很好理解. 这个最好参考一下我上面画的图.

 private final void  transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {int n = tab.length, stride;//todo  stride 在单核下直接等于 n,多核模式下为 (n>>>3)/NCPU,最小值是 16//todo  stride 可以理解为”步长“,有 n 个位置是需要进行迁移的,//todo  将这 n 个任务分为多个任务包,每个任务包有 stride 个任务, 最小值为 16if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)stride = MIN_TRANSFER_STRIDE; ////todo  如果 nextTab 为 null,先进行一次初始化//todo     前面我们说了,外围会保证第一个发起迁移的线程调用此方法时,参数 nextTab 为 null//todo        之后参与迁移的线程调用此方法时,nextTab 不会为 nullif (nextTab == null) {            // initiatingtry {@SuppressWarnings("unchecked")//todo  容量翻倍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;}//todo  nextTable 是 ConcurrentHashMap 中的属性nextTable = nextTab;//todo  transferIndex 也是 ConcurrentHashMap 的属性,用于控制迁移的位置, n 为 table 长度//todo  理解这个变量很重要!!!!!!!!!!!!!!!!!!//todo  transferIndex 为所有线程共享的变量,用于线程任务划分. 每个任务领取数据下标范围:   [transferIndex-stride , transferIndex ]  == [bound  ,  i]//todotransferIndex = n;}int nextn = nextTab.length;//todo  ForwardingNode 翻译过来就是正在被迁移的 Node//todo  这个构造方法会生成一个Node,key、value 和 next 都为 null,关键是 hash 为 MOVED//todo  后面我们会看到,原数组中位置 i 处的节点完成迁移工作后,//todo     就会将位置 i 处设置为这个 ForwardingNode,用来告诉其他线程该位置已经处理过了//todo     所以它其实相当于是一个标志。ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);//todo  advance 指的是做完了一个位置的迁移工作,可以准备做下一个位置的了boolean advance = true;  //todo 在table[i]的槽是否已完成迁移,这里的初始值不用理。boolean finishing = false; //todo table内所有桶都已迁移到nextTable标志位。//todo  i 是位置索引,bound 是边界,注意是从后往前for (int i = 0, bound = 0;;) {Node<K,V> f; int fh;//todo  下面这个 while 真的是不好理解//todo  advance 为 true 表示可以进行下一个位置的迁移了//todo    简单理解结局:i 指向了 transferIndex,bound 指向了 transferIndex-stridewhile (advance) {int nextIndex, nextBound;if (--i >= bound || finishing)advance = false;//todo  将 transferIndex 值赋给 nextIndex//todo  这里 transferIndex 一旦小于等于 0,说明原数组的所有位置都有相应的线程去处理了else if ((nextIndex = transferIndex) <= 0) {i = -1;advance = false;}else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex,nextBound = (nextIndex > stride ?nextIndex - stride : 0))) {//todo  确定当前线程每次分配的待迁移槽的范围 [bound, nextIndex]//todo  看括号中的代码,nextBound 是这次迁移任务的边界,注意,是从后往前bound = nextBound;i = nextIndex - 1;advance = false;}}//todo 当前线程自己的活已经做完或所有线程的活都已做完。if (i < 0 || i >= n || i + n >= nextn) {int sc;//todo 所有线程已干完活,最后才走这里。if (finishing) {//todo  所有的迁移操作已经完成nextTable = null;//todo  将新的 nextTab 赋值给 table 属性,完成迁移table = nextTab;//todo  重新计算 sizeCtl:n 是原数组长度,所以 sizeCtl 得出的值将是新数组长度的 0.75 倍sizeCtl = (n << 1) - (n >>> 1);return;}//todo  之前我们说过,sizeCtl 在迁移前会设置为 (rs << RESIZE_STAMP_SHIFT) + 2//todo  然后,每有一个线程参与迁移就会将 sizeCtl 加 1,//todo  这里使用 CAS 操作对 sizeCtl 进行减 1,代表做完了属于自己的任务if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {//todo  这个不理解//todo  存在任务没有完成扩容, 直接 return//todo   --|----------------------------------------------|--//todo     | 1000 0000 0001 1011 0000 0000 0000 0010      |    $2  ==>  sc ://todo     | 1000 0000 0001 1011 0000 0000 0000 0001      |    $2  ==>  sizeCtl ://todo     | 1000 0000 0001 1011 0000 0000 0000 0010      |    $3  ==>  sc ://todo     | 1000 0000 0001 1011 0000 0000 0000 0000      |    $3  ==>  sc -2//todo     | 0000 0000 0001 0000 0000 0000 0000 0000      |    $3  ==>  (16) << RESIZE_STAMP_SHIFT :// todo 这句不是很理解 !!!!!!!!!!!!!!!!!!!!!!!!//todo     | 1000 0000 0001 1011 0000 0000 0000 0001      |    $3  ==>  sizeCtl ://  CAS 更扩容阈值,在这里面sizectl值减一,说明新加入一个线程参与到扩容操作//todo   --|----------------------------------------------|-- System.out.println("$2  ==>  sc :  " + Integer.toBinaryString(sc) );if ((sc - 2) !=   (n) << RESIZE_STAMP_SHIFT)    {return;}//todo  到这里,说明 (sc - 2) == resizeStamp(n) << RESIZE_STAMP_SHIFT,//todo  也就是说,所有的迁移任务都做完了,也就会进入到上面的 if(finishing){} 分支了finishing = advance = true;i = n; // recheck before commit}}//todo  如果位置 i 处是空的,没有任何节点,那么放入刚刚初始化的 ForwardingNode ”空节点“selse if ((f = tabAt(tab, i)) == null)advance = casTabAt(tab, i, null, fwd);//todo  该位置处是一个 ForwardingNode,代表该位置已经迁移过了else if ((fh = f.hash) == MOVED)advance = true; // already processedelse {//todo  对数组该位置处的结点加锁,开始处理数组该位置处的迁移工作synchronized (f) {if (tabAt(tab, i) == f) {//todo 链表中的数据,分两种情况, 要么在原位置, 要么在 [原位置+原数组长度 ] 的位置Node<K,V> ln, hn;//todo  头结点的 hash 大于 0,说明是链表的 Node 节点if (fh >= 0) {//todo  循环遍历链表, 找到一个节点lastRun ,//   这样就会在链表中找到一个中间节点,lasRun 后面的 hash 值都一样 .//   或者说有那么一个中间节点,将链表一分为 2 .  前面位置不动, 后面放到  [原位置+原数组长度 ] 的位置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;}//todo 将 lastRun前面的节点拆分, 分别拼装成一个链表 ln , hnfor (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);}//todo  其中的一个链表放在新数组的位置 i 原位置setTabAt(nextTab, i, ln);//todo  另一个链表放在新数组的位置 i+n   新位置setTabAt(nextTab, i + n, hn);//todo  将原数组该位置处设置为 fwd,代表该位置已经处理完毕,//todo     其他线程一旦看到该位置的 hash 值为 MOVED,就不会进行迁移了setTabAt(tab, i, fwd);//todo  advance 设置为 true,代表该位置已经迁移完毕advance = true;}else if (f instanceof TreeBin) {//todo  红黑树的迁移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;}}//todo  如果一分为二后,节点数少于 8,那么将红黑树转换回链表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;//todo  将 ln 放置在新数组的位置 isetTabAt(nextTab, i, ln);//todo  将 hn 放置在新数组的位置 i+nsetTabAt(nextTab, i + n, hn);//todo  将原数组该位置处设置为 fwd,代表该位置已经处理完毕,//todo     其他线程一旦看到该位置的 hash 值为 MOVED,就不会进行迁移了setTabAt(tab, i, fwd);//todo  advance 设置为 true,代表该位置已经迁移完毕advance = true;}}}}}}

这个是 列表转换为 红黑树的方法:

/*** Replaces all linked nodes in bin at given index unless table is* too small, in which case resizes instead.*/private final void treeifyBin(Node<K,V>[] tab, int index) {Node<K,V> b; int n, sc;if (tab != null) {//todo  MIN_TREEIFY_CAPACITY 为 64//todo  所以,如果数组长度小于 64 的时候,其实也就是 32 或者 16 的时候,会进行数组扩容,并不会抓换为红黑树if ((n = tab.length) < MIN_TREEIFY_CAPACITY)//todo  扩容方法tryPresize(n << 1);//todo  b 是头结点else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {//todo  加锁synchronized (b) {if (tabAt(tab, index) == b) {//todo  下面就是遍历链表,建立一颗红黑树TreeNode<K,V> hd = null, tl = null;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);if ((p.prev = tl) == null)hd = p;elsetl.next = p;tl = p;}//todo  将红黑树设置到数组相应位置中setTabAt(tab, index, new TreeBin<K,V>(hd));}}}}}

get 方法:

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) {//todo  判断头结点是否就是我们需要的节点if ((eh = e.hash) == h) {if ((ek = e.key) == key || (ek != null && key.equals(ek)))return e.val;}//todo  如果头结点的 hash 小于 0,说明 正在扩容,或者该位置是红黑树else if (eh < 0)return (p = e.find(h, key)) != null ? p.val : null;//todo  遍历链表while ((e = e.next) != null) {if (e.hash == h &&((ek = e.key) == key || (ek != null && key.equals(ek))))return e.val;}}return null;}

获取数组下标值的方法( cas 锁 ):

   @SuppressWarnings("unchecked")//todo 获取值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);}static 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);}static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);}

这个是size 获取的最重要的方法:

// See LongAdder version for explanationprivate final void fullAddCount(long x, boolean wasUncontended) {int h;//todo 初始化操作if ((h = ThreadLocalRandom.getProbe()) == 0) {//todo 强制初始化ThreadLocalRandom.localInit();      // force initializationh = ThreadLocalRandom.getProbe();wasUncontended = true;}//todo 如果最后一个插槽非空,则为真boolean collide = false;                // True if last slot nonempty//todo 自旋for (;;) {CounterCell[] as; CounterCell a; int n; long v;//todo counterCells已经初始化if ((as = counterCells) != null && (n = as.length) > 0) {//todo 某个元素为空if ((a = as[(n - 1) & h]) == null) {//todo 没有线程操作 cellsBusyif (cellsBusy == 0) {            // Try to attach new Cell//todo 创建一个新的 CounterCell 对象CounterCell r = new CounterCell(x); // Optimistic createif (cellsBusy == 0 &&U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {boolean created = false;try {               // Recheck under lock//todo 在锁定下重新检查CounterCell[] rs; int m, j;//todo 将counterCells 存入if ((rs = counterCells) != null &&(m = rs.length) > 0 &&rs[j = (m - 1) & h] == null) {rs[j] = r;created = true;}} finally {cellsBusy = 0;}// todo 创建元素CounterCell 完成if (created)break;//todo 如果没有添加CounterCell 成功, 则继续  自旋锁continue;           // Slot is now non-empty}}collide = false;}else if (!wasUncontended)       // CAS already known to failwasUncontended = true;      // Continue after rehash//todo CounterCell 下标有值,  直接刚更新值else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))break;//todo counterCells 不等于 as  或者 数组长度 大于 CPU 的核心数else if (counterCells != as || n >= NCPU)//todo 不需要扩容collide = false;            // At max size or staleelse if (!collide)collide = true;//todo 代表没有其他线程 操作 的话else if (cellsBusy == 0 &&U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {try {//todo 以上操作都失败, 那么就扩容吧if (counterCells == as) {// Expand table unless staleCounterCell[] rs = new CounterCell[n << 1];//todo 迁移数据for (int i = 0; i < n; ++i)rs[i] = as[i];counterCells = rs;}} finally {cellsBusy = 0;}collide = false;continue;                   // Retry with expanded table}h = ThreadLocalRandom.advanceProbe(h);}//todo cellsBusy可以理解为锁?//todo   cellsBusy == 0 代表未操作 ,   操作中 1 , 初始化之后更改为 0else if (cellsBusy == 0 && counterCells == as &&U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {boolean init = false;try {//todo  // Initialize tableif (counterCells == as) {CounterCell[] rs = new CounterCell[2];//todo  x, 当前元素个数rs[h & 1] = new CounterCell(x);counterCells = rs;//todo 初始化完成init = true;}} finally {//todo 释放锁cellsBusy = 0;}//todo 如果初始化完成, 则退出if (init)break;}//todo 如果竞争很激烈 [扩容,添加都失败], 那么,直接把  数量加到 BASECOUNT里面 成功的话,直接跳出循环else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))break;                          // Fall back on using base}}

篇幅的问题.   Node 实体 , 和 TreeBin 实体我就不放了. 自己查看源码 ...............

JDK1.8 ConcurrentHashMap 源码解析相关推荐

  1. ConcurrentHashMap源码解析——基于JDK1.8

    ConcurrentHashMap源码解析--基于JDK1.8 前言 这篇博客不知道写了多久,总之就是很久,头都炸了.最开始阅读源码时确实是一脸茫然,找不到下手的地方,真是太难了.下面的都是我自己阅读 ...

  2. ConcurrentHashMap源码解析_02 预热(内部一些小方法分析)

    前面一篇文章中介绍了并发HashMap的主要成员属性,内部类和构造函数,下面在正式分析并发HashMap成员方法之前,先分析一些内部类中的字方法函数: 首先来看下ConcurrentHashMap内部 ...

  3. ConcurrentHashMap源码解析_01 成员属性、内部类、构造方法分析

    文章参考:小刘源码 ConcurrentHashMap源码解析_01 成员属性.内部类.构造方法分析 1.简介 ConcurrentHashMap是HashMap的线程安全版本,内部也是使用(数组 + ...

  4. 面试官系统精讲Java源码及大厂真题 - 16 ConcurrentHashMap 源码解析和设计思路

    16 ConcurrentHashMap 源码解析和设计思路 与有肝胆人共事,从无字句处读书. 引导语 当我们碰到线程不安全场景下,需要使用 Map 的时候,我们第一个想到的 API 估计就是 Con ...

  5. ConcurrentHashMap源码解析(1)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 注:在看这篇文章之前,如果对HashMap的层不清楚的话,建议先去看看HashMap源码解析. http:/ ...

  6. JDK8中ConcurrentHashMap源码解析

    在介绍ConcurrentHashMap源码之前,首先需要了解以下几个知识 1.JDK1.8中ConcurrentHashMap的基本结构 2.并发编程的三个概念:可见性,原子性,有序性 3.CAS( ...

  7. hashmap与concurrenthashmap源码解析

    hashmap源码解析转载:http://www.cnblogs.com/ITtangtang/p/3948406.html 一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此 ...

  8. 数据结构算法 - ConcurrentHashMap 源码解析

    Linux编程 点击右侧关注,免费入门到精通! 作者丨红橙Darren https://www.jianshu.com/p/0b452a6e4f4e 五个线程同时往 HashMap 中 put 数据会 ...

  9. 并发编程(十六)——java7 深入并发包 ConcurrentHashMap 源码解析

    以前写过介绍HashMap的文章,文中提到过HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容 ...

最新文章

  1. keil5图标变成白色_PPT中高大上的图标是怎么做的?
  2. 多设备同步表数据_利用Excel数据透视表解决两份报表数据不同步问题
  3. 关于Spring底层原理面试的那些问题,你是不是真的懂Spring?
  4. Docker 环境下如何 安装 Zookeeper
  5. 用python批量下载网络图片_python批量下载图片的三种方法
  6. 使用jQuery Html() 作为客户端htmlEncode的问题
  7. 日志jar包冲突,不打印日志。
  8. Spring ApplicationListener 事件监听器,能监听容器中所有实例
  9. 华为路由器接口如何区分_华为新一代路由评测,自带NFC,一碰就能联网
  10. 2016--在技术的浪潮中自我实现
  11. 《半小时漫画中国哲学史》读书摘记
  12. java实现Prim算法
  13. LIS3DH(3轴加速度计)使用
  14. 【MySQL】多表联合查询、连接查询、子查询
  15. 成组链接法 恩赐解脱
  16. 安卓耗电监控app_拍照成罪魁祸首 十大安卓耗电App排行公布
  17. selenium中ByChained方法
  18. phpMyAdmin 尝试连接到 MySQL 服务器,但服务器拒绝连接。
  19. PDF文件限制密码如何取消
  20. 干洗店收银系统应具备的功能

热门文章

  1. linux无法连接网络解决方案
  2. 计算机未来pdf,计算机科学理论过去、现在与未来.pdf
  3. vue仿今日头条_vue 仿今日头条 - osc_isfcy2fi的个人空间 - OSCHINA - 中文开源技术交流社区...
  4. 国内的linux系统,盘点2011国内仅存的几款Linux操作系统
  5. 刷题总结——分配笔名(51nod1526 trie树)
  6. spark读取PMML文件
  7. 【风控】如何判断用户的还款能力和还款意愿?
  8. java重定向cookie_在java中,JSP重定向,转发,Cookie,session
  9. 使用css实现一个圆形头像框效果
  10. Linux (Android) 串口通信教程