在介绍ConcurrentHashMap源码之前,很有必要复习下java并发编程中的一些基础知识,比如内存模型等。存储模型并发编程中的三个概念
1、原子性
2、可见性
3、重排序对HashMap在jdk8有所了解对CAS有所了解对内置锁和显示锁等有所了解

jdk8对ConcurrentHashMap做了很大的调整,首先因为HashMap在jdk8已经做了数据结构上的优化,增加了红黑树,详情可以参考我之前的博客。所以,jdk7针对ConcurrentHashMap的改进,主要是增加了分段锁Segment对HashEntity的控制,完美的解决了HashMap的安全问题,在JMM中有个名称叫安全发布,已经不适用了。那么,在jdk8如果保持性能的情况下对其进行修改了?它到底做了那些事情呢?

因为ConcurrentHashMap涉及的内容太多,jdk8有六千多行代码,jdk7才一两千行吧。所以我在想怎么一步步的对其进行剖解,最后我还是觉得按照程序的思路来吧,首先我们跑个ConcurrentHashMap的程序,然后进行调试,来一步步展开。

public static void main(String[] args) {Map<String, String> cm = new ConcurrentHashMap<String, String>();for (int i = 0; i < 14; i++) {cm.put("key_" + i, "huaizuo_" + i);}
}

成员变量与hashmap大都一样,主要说下不同的地方:

/*** races. Updated via CAS.* 记录容器的容量大小,通过CAS更新*/
private transient volatile long baseCount;/*** 这个sizeCtl是volatile的,那么他是线程可见的,一个思考:它是所有修改都在CAS中进行,但是sizeCtl为什么不设计成LongAdder(jdk8出现的)类型呢?* 或者设计成AtomicLong(在高并发的情况下比LongAdder低效),这样就能减少自己操作CAS了。** 来看下注释,当sizeCtl小于0说明有多个线程正则等待扩容结果,参考transfer函数** sizeCtl等于0是默认值,大于0是扩容的阀值*/
private transient volatile int sizeCtl;/***  自旋锁 (锁定通过 CAS) 在调整大小和/或创建 CounterCells 时使用。 在CounterCell类更新value中会使用,功能类似显示锁和内置锁,性能更好*  在Striped64类也有应用*/
private transient volatile int cellsBusy;

还有最重要的节点类Node,注意val和next是volatile类型

static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;volatile V val;volatile Node<K,V> next;Node(int hash, K key, V val, Node<K,V> next) {this.hash = hash;this.key = key;this.val = val;this.next = next;}

接下来我们要把元素put到ConcurrentHashMap中了,那么我们来看下putVal的源码吧

/** Implementation for put and putIfAbsent */final V putVal(K key, V value, boolean onlyIfAbsent) {if (key == null || value == null) throw new NullPointerException();/*** 获取hash值 该hash值永远为正(需要注意哪里被更新为0)*/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();/***  n-1 & hash 相当于 hash与n取模*  头节点为空*/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}//该bucket正在transferelse if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);else {V oldVal = null;/*** 内置锁synchronized锁住了f,因为f是指定特定的tab[i]的* 所以就锁住了整行链表,这个设计跟分段锁类似* 只是其他读取操作需要用cas来保证*/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;/*** e不断寻找后继节点, binCount等于节点个数*/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)treeifyBin(tab, i);if (oldVal != null)return oldVal;break;}}}addCount(1L, binCount);return null;}

因为这些操作都是按照CAS的,其中关键部分已经做了注释,要正确取到真实数据需要知道变量所在的内存偏移量。

@SuppressWarnings("unchecked")
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);
}/**但是这边为什么i要等于((long)i << ASHIFT) + ABASE呢,计算偏移量*ASHIFT是指tab[i]中第i个元素在相对于数组第一个元素的偏移量,而ABASE就算第一数组的内存素的偏移地址*所以呢,((long)i << ASHIFT) + ABASE就算i最后的地址* 那么compareAndSwapObject的作用就算tab[i]和c比较,如果相等就tab[i]=v否则tab[i]=c;
*/
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);
}

关于sun.misc.Unsafe

还是继续看put源码,看到//a注释,当(fh = f.hash) == MOVED,说明f.hash值为-1(MOVED为-1的final),那么如果hash什么时候回等于-1呢?为什么会有-1这种情况呢?这要涉及到ForwardingNode<K,V>类

static final class ForwardingNode<K,V> extends Node<K,V> {final Node<K,V>[] nextTable;ForwardingNode(Node<K,V>[] tab) {//MOVED 位-1,说明ForwardNode的节点的hash值为-1super(MOVED, null, null, null);this.nextTable = tab;}

这个类是继承Node类的,他在初始化的时候hash值传了MOVED,我们知道ConcurrentHashMap在的数据结构是Table[]和链表组成,所以如果Table节点是ForwardNode节点的话那么Hash的值就等于-1,那么什么时候Node会变成ForwardNode呢?就是在扩容的时候,旧的Table的节点会临时用ForwardNode代替。待会会介绍。

我们还是继续一步步看代码,看inputVal的注释a,这个方法helpTransfer,如果线程进入到这边说明已经有其他线程正在做扩容操作,这个是一个辅助方法

/*** 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;//sc + 1 少一个线程进行resizeif (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {transfer(tab, nextTab);break;}}return nextTab;}return table;}

这边如果table的某一个节点对应的链表超过一定的长度之后,就要把链表转化为红黑树的操作我就不详细的在这边文章介绍了,对于转化的操作其实和HashMap是一样的,但是这里涉及到并发,它其实也是通过synchronized和CAS来控制并发的。好了,当我们的putVal执行到addCount的时候

private final void addCount(long x, int check) {CounterCell[] as;long b, s;if ((as = counterCells) != null ||!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {CounterCell a;long v;int m;boolean uncontended = true;/*** 每个线程都会通过ThreadLocalRandom.getProbe() & m寻址找到属于它的CounterCell,* 然后进行计数。* ThreadLocalRandom是一个线程私有的伪随机数生成器,* 每个线程的probe都是不同的(这点基于ThreadLocalRandom的内部实现,* 它在内部维护了一个probeGenerator,* 这是一个类型为AtomicInteger的静态常量,* 每当初始化一个ThreadLocalRandom时probeGenerator都会先自增一个常量然后返回的整数即为当前线程的probe,* probe变量被维护在Thread对象中)* 可以认为每个线程的probe就是它在CounterCell数组中的hash code。*/if (as == null || (m = as.length - 1) < 0 ||(a = as[ThreadLocalRandom.getProbe() & m]) == null ||!(uncontended =U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {//多线程CAS发生失败的时候执行fullAddCount(x, uncontended);return;}if (check <= 1)return;s = sumCount();}if (check >= 0) {Node<K,V>[] tab, nt; int n, sc;/*** 当条件满足开始扩容* 当sumCount里面的个数及size 大于 阈值时发生扩容*/while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&(n = tab.length) < MAXIMUM_CAPACITY) {int rs = resizeStamp(n); // 计算本次扩容生成戳/*** 如果小于0说明已经有线程在进行扩容操作了*/if (sc < 0) {/*** 如下的情况说明已经有在扩容或者多线程进行了扩容,其他线程直接break不要进入扩容操作*/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);}/*** 这个时候sizeCtl已经等于(rs << RESIZE_STAMP_SHIFT) + 2等于一个大的负数,* 这边加上2很巧妙,因为transfer后面对sizeCtl--操作的时候* 最多只能减两次就结束*/else if (U.compareAndSwapInt(this, SIZECTL, sc,(rs << RESIZE_STAMP_SHIFT) + 2))transfer(tab, null);s = sumCount();}}}

看上面注释1,每次都会对baseCount 加1,如果并发竞争太大,那么可能导致U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x) 失败,那么为了提高高并发的时候baseCount可见性失败的问题,又避免一直重试,这样性能会有很大的影响,那么在jdk8的时候是有引入一个类Striped64,其中LongAdder和DoubleAdder就是对这个类的实现。这两个方法都是为解决高并发场景而生的,是AtomicLong的加强版,AtomicLong在高并发场景性能会比LongAdder差。但是LongAdder的空间复杂度会高点。

// See LongAdder version for explanationprivate final void fullAddCount(long x, boolean wasUncontended) {int h;/*** 获取当前线程的probe值作为hash值,如果0则强制初始化当前线程的Probe值,初始化的probe值不为0*/if ((h = ThreadLocalRandom.getProbe()) == 0) {ThreadLocalRandom.localInit();      // force initializationh = ThreadLocalRandom.getProbe();wasUncontended = true;}boolean collide = false;                // True if last slot nonemptyfor (;;) {CounterCell[] as;CounterCell a;int n;long v;if ((as = counterCells) != null && (n = as.length) > 0) {if ((a = as[(n - 1) & h]) == null) {if (cellsBusy == 0) {            // Try to attach new CellCounterCell r = new CounterCell(x); // Optimistic createif (cellsBusy == 0 &&U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {boolean created = false;try {               // Recheck under lockCounterCell[] 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 failwasUncontended = true;      // Continue after rehashelse if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) //对cell的value值进行累计x(1)break;else if (counterCells != as || n >= NCPU)collide = false;            // At max size or stale// 表明as已经过时,说明cells已经初始化完成,看下面,重置collide为false表明已经存在竞争else if (!collide)collide = true;else if (cellsBusy == 0 &&U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {try {if (counterCells == as) {// Expand table unless staleCounterCell[] rs = new CounterCell[n << 1];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);}else if (cellsBusy == 0 && counterCells == as &&U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {boolean init = false;try {                           // Initialize tableif (counterCells == as) {CounterCell[] rs = new CounterCell[2];rs[h & 1] = new CounterCell(x);counterCells = rs;init = true;}} finally {cellsBusy = 0;}if (init)break;}else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))break;                          // Fall back on using base}}

源码注释写着See LongAdder version for explanation。我上面已经做了注释了,就不做更多解释了。

回到addCount来,我们每次竟来都对baseCount进行加1当达到一定的容量时,就需要对table进行扩容。扩容方法就是transfer,这个方法稍微复杂一点,大部分的代码我都做了注释

/*** 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;int stride;if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)stride = MIN_TRANSFER_STRIDE; // subdivide rangeif (nextTab == null) {            // initiatingtry {@SuppressWarnings("unchecked")Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];//先扩容2倍nextTab = nt;} catch (Throwable ex) {      // try to cope with OOMEsizeCtl = Integer.MAX_VALUE;return;}nextTable = nextTab;transferIndex = n;}int nextn = nextTab.length;//创建「先行」nodeForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);boolean advance = true;//循环的关键变量,判断是否已经扩容完成,完成就return,退出循环boolean finishing = false; // to ensure sweep before committing nextTabfor (int i = 0, bound = 0;;) { // 注意这两个游标的遍历方式Node<K,V> f;int fh;while (advance) {int nextIndex;int nextBound;// 当finishing 为trueif (--i >= bound || finishing)advance = false;/*** 或者resizing时 transferIndex == 0 说明resize完成;* transferIndex 默认为16*/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;}}/*** i<0说明已经遍历完旧的数组tab;i>=n什么时候有可能呢?在下面看到i=n,所以目前i最大应该是n吧。* i+n>=nextn,nextn=nextTab.length,所以如果满足i+n>=nextn说明已经扩容完成*/if (i < 0 || i >= n || i + n >= nextn) {int sc;if (finishing) {nextTable = null;table = nextTab;sizeCtl = (n << 1) - (n >>> 1); //新表的阈值return;}//利用CAS方法更新这个扩容阈值,在这里面sizectl值减一,说明新加入一个线程参与到扩容操作if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {/***  如果有多个线程进行扩容,*  那么这个值在第二个线程以后就不会相等,*  因为sizeCtl已经被减1了,*  所以后面的线程就只能直接返回,始终保证只有一个线程执行了上段代码*/if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)return;finishing = advance = true; //finishing和advance保证线程已经扩容完成了可以退出循环i = n; // recheck before commit}}/*** 如果tab[i]为null,那么就把fwd插入到tab[i],表明这个节点已经处理过了*/else if ((f = tabAt(tab, i)) == null)advance = casTabAt(tab, i, null, fwd);/*** 那么如果f.hash=-1的话说明该节点为ForwardingNode,说明该节点已经处理过了*/else if ((fh = f.hash) == MOVED)advance = true; // already processedelse {synchronized (f) {if (tabAt(tab, i) == f) {Node<K,V> ln, hn;if (fh >= 0) {//相当于与新table长度进行取模int runBit = fh & n;Node<K,V> lastRun = f;for (Node<K,V> p = f.next; p != null; p = p.next) {//依次遍历进行与新table长度取模,判断在原位置还是i + n位置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);}setTabAt(nextTab, i, ln);setTabAt(nextTab, i + n, hn);/*** 把已经替换的节点的旧tab的i的位置用fwd替换,fwd包含nextTab*/setTabAt(tab, i, fwd);advance = true;}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;}}//判断扩容后是否还需要红黑树结构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;setTabAt(nextTab, i, ln);setTabAt(nextTab, i + n, hn);setTabAt(tab, i, fwd);advance = true;}}}}}}

值得细细品味的是,transfer的for循环是倒叙的,说明对table的遍历是从table.length-1开始到0的。我觉得这段代码写得太牛逼了,特别是

//利用CAS方法更新这个扩容阈值,在这里面sizectl值减一,说明新加入一个线程参与到扩容操作,参考sizeCtl的注释
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {//如果有多个线程进行扩容,那么这个值在第二个线程以后就不会相等,因为sizeCtl已经被减1了,所以后面的线程就只能直接返回,始终保证只有一个线程执行了 a(上面注释a)if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)return;finishing = advance = true;//finishing和advance保证线程已经扩容完成了可以退出循环i = n; // recheck before commit
}

反正很多地方值得细细精读。

那么我想我已经把ConcurrentHashMap的一部分内容讲完,包括添加元素putVal,扩容transfer等。那么现在我们来看下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) {if ((eh = e.hash) == h) {if ((ek = e.key) == key || (ek != null && key.equals(ek)))return e.val;}else if (eh < 0)//如果eh=-1就说明e节点为ForWordingNode,这说明什么,说明这个节点已经不存在了,被另一个线程正则扩容//所以要查找key对应的值的话,直接到新newtable找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;
}

这个get请求,我们需要cas来保证变量的原子性。如果tab[i]正被锁住,那么CAS就会失败,失败之后就会不断的重试。这也保证了get在高并发情况下不会出错。

我们来分析下到底有多少种情况会导致get在并发的情况下可能取不到值。1、一个线程在get的时候,另一个线程在对同一个key的node进行remove操作;2、一个线程在get的时候,另一个线程正则重排table。可能导致旧table取不到值。

那么本质是,我在get的时候,有其他线程在对同一桶的链表或树进行修改。那么get是怎么保证同步性的呢?我们看到e = tabAt(tab, (n - 1) & h)) != null,在看下tablAt到底是干嘛的:

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);
}

它是对tab[i]进行原子性的读取,因为我们知道putVal等对table的桶操作是有加锁的,那么一般情况下我们对桶的读也是要加锁的,但是我们这边为什么不需要加锁呢?因为我们用了Unsafe的getObjectVolatile,因为table是volatile类型,所以对tab[i]的原子请求也是可见的。因为如果同步正确的情况下,根据happens-before原则,对volatile域的写入操作happens-before于每一个后续对同一域的读操作。

那么好奇的我翻看了下jdk7的get方法是怎么处理的,因为我们知道jdk7是没有用到CAS操作和Unsafe类的,下面是jdk7的get方法

V get(Object key, int hash) { if(count != 0) {       // 首先读 count 变量HashEntry<K,V> e = getFirst(hash); while(e != null) { if(e.hash == hash && key.equals(e.key)) { V v = e.value; if(v != null)            return v; // 如果读到 value 域为 null,说明发生了重排序,加锁后重新读取return readValueUnderLock(e); } e = e.next; } } return null; }

为什么我们在get的时候需要判断count不等于0呢?如果是在HashMap的源码中是没有这个判断的,不用判断不是也是可以的吗?这个就是用到线程安全发布情况下happens-before原则之volatile变量法则:对volatile域的写入操作happens-before于每一个后续对同一域的读操作

如果有读java并发编程实战这本书第16的话,我们知道要有时候我们需要“驾驭”在同步之上。为了满足happens-before,这个需要结合“程序次序法则”与另外一种次序法则(通常是“监视器锁法则或“volatile变量法则”)来对访问变量的操作进行排序,否则就用锁来保护它
对于上图A操作happens-before 于B,C操作happens-before于D。因为count是volatile,所以对count的写要happens-before于读操作。所以B操作happens-before于C。根据传递性,连接上面三个 happens-before 关系得到:A appens-before 于 B; B appens-before C;C happens-before D。也就是说:写线程 M 对链表做的结构性修改,在读线程 N 读取了同一个 volatile 变量后,对线程 N 也是可见的了。虽然线程 N 是在未加锁的情况下访问链表。Java 的内存模型可以保证:只要之前对链表做结构性修改操作的写线程 M 在退出写方法前写 volatile 型变量 count,读线程 N 在读取这个 volatile 型变量 count 后,就一定能“看到”这些修改。这个特性和前面介绍的 HashEntry 对象的不变性相结合,使得在 ConcurrentHashMap 中,读线程在读取散列表时,基本不需要加锁就能成功获得需要的值。这两个特性相配合,不仅减少了请求同一个锁的频率(读操作一般不需要加锁就能够成功获得值),也减少了持有同一个锁的时间(只有读到 value 域的值为 null 时 , 读线程才需要加锁后重读)。

这个设计非常精彩,要对JMM非常熟悉。跟jdk8的处理手法有异曲同工之妙。

其他的revove修改的操作跟putVal操作类似这里就不做分析了。

欢迎各位关注我个人的公众号,会不定期更新jdk的源码:

concurrenthashmap是什么锁_JDK1.8 util-concurrent-ConcurrentHashMap源码分析相关推荐

  1. java.util.concurrent 包源码分析之Fork/Join框架

    在JDK7引入了Fork/Join框架,所谓Fork/Join框架,个人理解,有一种分治的策略在里边:Fork分解任务成独立的子任务,用多线程去执行这些子任务,Join合并子任务的结果.这样就能使用多 ...

  2. java.util.concurrent.FutureTask 源码

    2019独角兽企业重金招聘Python工程师标准>>> 线程池相关 源码: package java.util.concurrent;import java.util.concurr ...

  3. java.util.concurrent.ExecutorCompletionService 源码

    2019独角兽企业重金招聘Python工程师标准>>> 线程池相关 源码: package java.util.concurrent;public class ExecutorCom ...

  4. 【阅读源码系列】ConcurrentHashMap源码分析(JDK1.7和1.8)

    个人学习源码的思路: 使用ctrl+单机进入源码,并阅读源码的官方文档–>大致的了解一下此类的特点和功能 使用ALIT+7查看类中所有方法–>大致的看一下此类的属性和方法 找到重要方法并阅 ...

  5. 【JUC】JDK1.8源码分析之ConcurrentHashMap

    一.前言 最近几天忙着做点别的东西,今天终于有时间分析源码了,看源码感觉很爽,并且发现ConcurrentHashMap在JDK1.8版本与之前的版本在并发控制上存在很大的差别,很有必要进行认真的分析 ...

  6. HashMap、ConcurrentHashMap(1.7、1.8)源码分析 + 红黑树

    个人博客欢迎访问 总结不易,如果对你有帮助,请点赞关注支持一下 微信搜索程序dunk,关注公众号,获取博客源码 序号 内容 1 Java基础面试题 2 JVM面试题 3 Java并发编程面试 4 计算 ...

  7. Java并发基础:了解无锁CAS就从源码分析

    CAS的全称为Compare And Swap,直译就是比较交换.是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值,其实现方式是基于硬件平台的汇编指令,在i ...

  8. Java源码详解六:ConcurrentHashMap源码分析--openjdk java 11源码

    文章目录 注释 类的继承与实现 数据的存储 构造函数 哈希 put get 扩容 本系列是Java详解,专栏地址:Java源码分析 ConcurrentHashMap 官方文档:ConcurrentH ...

  9. Java并发基础:了解无锁CAS就从源码分析 1

    CAS的全称为Compare And Swap,直译就是比较交换.是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值,其实现方式是基于硬件平台的汇编指令,在i ...

  10. Java Review - 并发组件ConcurrentHashMap使用时的注意事项及源码分析

    文章目录 概述 案例 原因分析 修复 小结 概述 ConcurrentHashMap虽然为并发安全的组件,但是使用不当仍然会导致程序错误.我们这里通过一个简单的案例来复现这些问题,并给出开发时如何避免 ...

最新文章

  1. python excel数据框_使用python pandas使用新数据框附加现有excel表
  2. dokuwiki导航的研究
  3. spring30: 事务
  4. vivo应用商店电脑版_VIVO应用商店代理商江湖的那些关系
  5. C++ STL 迭代器5种类型 简介
  6. Java 14 发布了,再也不怕 NullPointerException 了!
  7. gulp-sass的有关问题
  8. Oracle用imp和exp实现数据的导入和导出
  9. 华为手机老是显示不到服务器,老显示连接不到服务器
  10. 关于Ubuntu下apt的一些用法及和yum的比较
  11. Android Material Design按钮样式设计
  12. Visio2016 安装教程
  13. RFID技术中各频段电子标签的特点及其应用领域
  14. 单片微机计算机原理与接口技术高峰,单片微机原理与接口技术
  15. 模板消息php 群发,微信公众号模板消息群发php代码示例
  16. python----根据共振峰频率绘制二阶谐振曲线
  17. Excel快速删除空白行与调整行高列宽的方法,学会了很实用
  18. win10系统映像恢复
  19. 转载1:拓扑结构介绍及其种类
  20. 小而美的城市,比鸭脖更诱人

热门文章

  1. PHP水仙花问题解法之一
  2. mysql命令:set sql_log_bin=on/off
  3. ubuntu linux 启用root用户登录
  4. 协程分析之 context 上下文切换
  5. 【论文写作】课程指导平台的开发中系统部分代码如何写
  6. 技嘉 b360m d3h-cf efi_技嘉Geforce RTX 3080 GAMING OC 10G评测:性能入魔,方为魔鹰_显卡...
  7. android布局边缘加深,Android布局属性详解
  8. 安装了mySQL后怎么导入数据_mysql安装、配置、导入数据库
  9. mysql数据库腾讯云添加用户,解决腾讯云cdb的基础版mysql不支持新建账号
  10. 无法下载linux系统的驱动精灵,【驱动精灵和搜狗输入法 For Linux哪个好用】驱动精灵和搜狗输入法 For Linux对比-ZOL下载...