java8 ConcurrentHashMap原始碼解析
Java8較java7的改進:
改進一:取消segments欄位,直接採用transient volatile HashEntry<K,V> table儲存資料,採用table陣列元素作為鎖,從而實現了對每一行資料進行加鎖,進一步減少併發衝突的概率。
/** * The array of bins. Lazily initialized upon first insertion. * Size is always a power of two(2的冪). Accessed directly by iterators. */ transient volatile Node<K,V>[] table;
改進二:將原先table陣列+單向連結串列的資料結構,變更為table陣列+單向連結串列+紅黑樹的結構。對於hash表來說,最核心的能力在於將key hash之後能均勻的分佈在陣列中。如果hash之後雜湊的很均勻,那麼table陣列中的每個佇列長度主要為0或者1。但實際情況並非總是如此理想,雖然ConcurrentHashMap類預設的載入因子為0.75,但是在資料量過大或者運氣不佳的情況下,還是會存在一些佇列長度過長的情況,如果還是採用單向列表方式,那麼查詢某個節點的時間複雜度為O(n);因此,對於個數超過8(預設值)的列表,jdk1.8中採用了紅黑樹的結構,那麼查詢的時間複雜度可以降低到O(logN),可以改進效能。
重要屬性:
/** * 這個sizeCtl是volatile的,那麼他是執行緒可見的,一個思考:它是所有修改都在CAS中進行,但是sizeCtl為什麼不設計成LongAdder(jdk8出現的)型別呢? * 或者設計成AtomicLong(在高併發的情況下比LongAdder低效),這樣就能減少自己操作CAS了。 * * 預設為0,用來控制table的初始化和擴容操作,具體應用在後續會體現出來。 * -1 代表table正在初始化 * -N 表示有N-1個執行緒正在進行擴容操作 * 其餘情況: *1、如果table未初始化,表示table需要初始化的大小。 *2、如果table初始化完成,表示table的容量,預設是table大小的0.75 倍,居然用這個公式算0.75(n - (n >>> 2))。 **/ private static final long SIZECTL; private static final long TRANSFERINDEX; /** * races. Updated via CAS. * 記錄容器的容量大小,通過CAS更新 */ private static final long BASECOUNT; /** * 自旋鎖 (鎖定通過 CAS) 在調整大小和/或建立 CounterCells 時使用。 在CounterCell類更新value中會使用,功能類似顯示鎖和內建鎖,效能更好 * 在Striped64類也有應用 */ private static final long CELLSBUSY; private static final long CELLVALUE; private static final long ABASE; private static final int ASHIFT; /** * Node:儲存key,value及key的hash值的資料結構。其中value和next都用volatile修飾,保證併發的可見性。 * @param <K> * @param <V> */ static class Node<K,V> implements Entry<K,V> { final int hash; final K key; volatile V val; .. volatile Node<K,V> next; } /** * ForwardingNode:一個特殊的Node節點,hash值為-1,其中儲存nextTable的引用。 * @param <K> * @param <V> */ static final class ForwardingNode<K,V> extends Node<K,V> { final Node<K,V>[] nextTable; … }
建構函式:
/**
*initialCapacity 初始化容量
**/
public ConcurrentHashMap(int initialCapacity)
/**
*
*建立與給定map具有相同對映的新map
**/
public ConcurrentHashMap(Map<? extends K, ? extends V> m)
/**
*initialCapacity 初始容量
*loadFactor 負載因子,當容量達到initialCapacity*loadFactor時,執行擴容
**/
public ConcurrentHashMap(int initialCapacity, float loadFactor)
/**
*initialCapacity 初始容量
*loadFactor 負載因子
*concurrencyLevel 預估的併發更新執行緒數
**/
public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel)
方法:
put:
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());//對hashCode進行再雜湊,演算法為(h ^ (h >>> 16)) & HASH_BITS
int binCount = 0;
//這邊加了一個迴圈,就是不斷的嘗試,因為在table的初始化和casTabAt用到了compareAndSwapInt、compareAndSwapObject
//因為如果其他執行緒正在修改tab,那麼嘗試就會失敗,所以這邊要加一個for迴圈,不斷的嘗試
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 如果table為空,初始化;否則,根據hash值計算得到陣列索引i,如果tab[i]為空,直接新建節點Node即可。注:tab[i]實質為連結串列或者紅黑樹的首節點。
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, null)))
break; // no lock when adding to empty bin
}
// 如果tab[i]不為空並且hash值為MOVED(-1),說明該連結串列正在進行transfer操作,返回擴容完成後的table
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
// 針對首個節點進行加鎖操作,而不是segment,進一步減少執行緒衝突
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 如果在連結串列中找到值為key的節點e,直接設定e.val = value即可。
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
// 如果沒有找到值為key的節點,直接新建Node並加入連結串列即可。
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 如果首節點為TreeBin型別,說明為紅黑樹結構,執行putTreeVal操作。
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;
}
}
}
// 計數增加1,有可能觸發transfer操作(擴容)。
addCount(1L, binCount);
return null;
}
helpTransfer:
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) {
//下面幾種情況和addCount的方法一樣,請參考addCount的備註
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;
}
tabAt:
@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);
}
addCount:
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
//U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x) 每次進來都baseCount都加1因為x=1
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {//1
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))) {
//多執行緒CAS發生失敗的時候執行
fullAddCount(x, uncontended);//2
return;
}
if (check <= 1)
return;
s = sumCount();
}
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) {//如果小於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的空間複雜度會高點。
我們每次進來都對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, stride;
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
if (nextTab == null) { // initiating
try {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME
sizeCtl = Integer.MAX_VALUE;
return;
}
nextTable = nextTab;
transferIndex = n;
}
int nextn = nextTab.length;
//構建一個連節點的指標,用於標識位
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
boolean advance = true;
boolean finishing = false; // to ensure sweep before committing nextTab
//迴圈的關鍵變數,判斷是否已經擴容完成,完成就return,退出迴圈
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
//迴圈的關鍵i,i--操作保證了倒序遍歷陣列
while (advance) {
int nextIndex, nextBound;
if (--i >= bound || finishing)
advance = false;
else if ((nextIndex = transferIndex) <= 0) {//nextIndex=transferIndex=n=tab.length(預設16)
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) {// a
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
//利用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
}
}
else if ((f = tabAt(tab, i)) == null)//如果tab[i]為null,那麼就把fwd插入到tab[i],表明這個節點已經處理過了
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED)//那麼如果f.hash=-1的話說明該節點為ForwardingNode,說明該節點已經處理過了
advance = true; // already processed
else {
synchronized (f) {
if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
if (fh >= 0) {
int runBit = fh & n;
Node<K,V> lastRun = f;
//這邊還對連結串列進行遍歷,這邊的的演算法和hashmap的演算法又不一樣了,這班是有點對半拆分的感覺
//把連結串列分表拆分為,hash&n等於0和不等於0的,然後分別放在新表的i和i+n位置
//次方法同hashmap的resize
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);
else
hn = 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;
else
loTail.next = p;
loTail = p;
++lc;
}
else {
if ((p.prev = hiTail) == null)
hi = p;
else
hiTail.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;
}
}
}
}
}
}
注意:如果連結串列結構中元素超過TREEIFY_THRESHOLD閾值,預設為8個,則把連結串列轉化為紅黑樹,提高遍歷查詢效率.接下來我們看看如何構造樹結構,程式碼如下:
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)
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;
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;
else
tl.next = p;
tl = p;
}
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}
可以看出,生成樹節點的程式碼塊是同步的,進入同步程式碼塊之後,再次驗證table中index位置元素是否被修改過。
1、根據table中index位置Node連結串列,重新生成一個hd為頭結點的TreeNode連結串列。
2、根據hd頭結點,生成TreeBin樹結構,並把樹結構的root節點寫到table的index位置的記憶體中,具體實現如下:
/**
* Creates bin with initial set of nodes headed by b.
*/
TreeBin(TreeNode<K,V> b) {
super(TREEBIN, null, null, null);
this.first = b;
TreeNode<K,V> r = null;
for (TreeNode<K,V> x = b, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
x.left = x.right = null;
if (r == null) {
x.parent = null;
x.red = false;
r = x;
}
else {
K k = x.key;
int h = x.hash;
Class<?> kc = null;
for (TreeNode<K,V> p = r;;) {
int dir, ph;
K pk = p.key;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
r = balanceInsertion(r, x);
break;
}
}
}
}
this.root = r;
assert checkInvariants(root);
}
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到底是幹嘛的:
@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);
}
它是對tab[i]進行原子性的讀取,因為我們知道putVal等對table的桶操作是有加鎖的,那麼一般情況下我們對桶的讀也是要加鎖的,但是我們這邊為什麼不需要加鎖呢?因為我們用了Unsafe的getObjectVolatile,因為table是volatile型別,所以對tab[i]的原子請求也是可見的。因為如果同步正確的情況下,根據happens-before原則,對volatile域的寫入操作happens-before於每一個後續對同一域的讀操作。所以不管其他執行緒對table連結串列或樹的修改,都對get讀取可見。
sun.misc.Unsafe類:
Unsafe類是什麼呢?java不能直接訪問作業系統底層,而是通過本地方法來訪問。Unsafe類提供了硬體級別的原子操作。Unsafe類在jdk 原始碼的多個類中用到,這個類的提供了一些繞開JVM的更底層功能,基於它的實現可以提高效率。但是,它是一把雙刃劍:正如它的名字所預示的那樣,它是Unsafe的,它所分配的記憶體需要手動free(不被GC回收)。Unsafe類,提供了JNI某些功能的簡單替代:確保高效性的同時,使事情變得更簡單。
//在o的offset偏移地址處,獲取volatile型別的物件
public native java.lang.Object getObjectVolatile(java.lang.Object o, long l);
//原子性的更新java變數
public final native boolean compareAndSwapObject(java.lang.Object o, long l, java.lang.Object o1, java.lang.Object o2);
/**
* Stores a reference value into a given Java variable, with volatile store
* semantics. Otherwise identical to
* {@link #putObject(Object, long, Object)}
*/
public native void putObjectVolatile(java.lang.Object o, long l, java.lang.Object o1);
/**
* Atomically update Java variable to <tt>x</tt> if it is currently holding
* <tt>expected</tt>.
*
* @return <tt>true</tt> if successful
*/
public final native boolean compareAndSwapLong(java.lang.Object o, long l, long l1, long l2);
相關推薦
java8 ConcurrentHashMap原始碼解析
浪費了“黃金五年”的Java程式設計師,還有救嗎? >>>
併發程式設計---ConcurrentHashMap原始碼解析
ConcurrentHashMap是java中為了解決HashMap不能支援高併發而設計的新的實現。 ConcurrentHashMap的類結構 public class ConcurrentHashMap<K,V> ext
ConcurrentHashMap原始碼解析(JDK1.8)
package java.util.concurrent; import java.io.ObjectStreamField; import java.io.Serializable; import java.lang.reflect.ParameterizedType;
Java8 HashMap原始碼解析
前言 Java7中的HashMap和Java8中的HashMap不太一樣,Java7中的HashMap主要是由陣列+連結串列組成的,而Java8中的 HashMap是由陣列+連結串列+紅黑樹組成的,當連結串列的長度超過8個時,就會轉為紅黑樹,降低查詢時的時間複
ConcurrentHashMap原始碼解析
初始化 先看看ConcurrentHashMap中幾個重要的屬性: // 初始化容量大小 static final int DEFAULT_INITIAL_CAPACITY = 16; //預設負載因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; //預
Java HashMap和ConcurrentHashMap原始碼解析
閱讀建議:四節基本上可以進行獨立閱讀,建議初學者可按照 Java7 HashMap -> Java7 ConcurrentHashMap -> Java8 HashMap -> Java8 ConcurrentHashMap 順序進行閱讀,可適
Java容器——HashMap(Java8)原始碼解析(二)
在前文中介紹了HashMap中的重要元素,現在萬事俱備,需要刨根問底看看實現了。HashMap的增刪改查,都離不開元素查詢。查詢分兩部分,一是確定元素在table中的下標,二是確定以指定下標元素為首的具體位置。可以抽象理解為二維陣列,第一個通過雜湊函式得來,第二個下標則是連結串列或紅黑樹來得到,下面
Java容器——HashMap(Java8)原始碼解析(一)
一 概述 HashMap是最常用的Java資料結構之一,是一個具有常數級別的存取速率的高效容器。相對於List,Set等,結構相對複雜,本篇我們先對HashMap的做一個基本說明,對組成元素和構造方法進行介紹。 二 繼承關係 首先看HashMap的繼承關係,比較簡單,實現了Map和序列化
ConcurrentHashMap原始碼解析(3)
此文已由作者趙計剛授權網易雲社群釋出。 歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。 4、get(Object key) 使用方法: map.get("hello"); 原始碼: ConcurrentHashMap的get(Object key)
第二章 ConcurrentHashMap原始碼解析
注:在看這篇文章之前,如果對HashMap的層不清楚的話,建議先去看看HashMap原始碼解析。 1、對於ConcurrentHashMap需要掌握以下幾點 Map的建立:ConcurrentHashMap() 往Map中新增鍵值對:即put(Object key, Object value)方
Java8 ThreadLocal 原始碼解析
前言 ThreadLocal ,像是一個神祕的黑衣人,令人望而生畏。唯有下定決心,一探究竟,方能解開他神祕的面紗、在Android中,Handler,EventBus,ConnectionPool 等等,都曾出現它的身影 是什麼東西? 看到Thread
Java併發程式設計與技術內幕:ConcurrentHashMap原始碼解析
摘要:本文主要講了Java中ConcurrentHashMap 的原始碼 ConcurrentHashMap 是java併發包中非常有用的一個類,在高併發場景用得非常多,它是執行緒安全的。要注意到雖然HashTable雖然也是執行緒安全的,但是它的效
ConcurrentHashMap原始碼解析 JDK8
一、簡介 上篇文章詳細介紹了HashMap的原始碼及原理,本文趁熱打鐵繼續分析ConcurrentHashMap的原理。 首先在看本文之前,希望對HashMap有一個詳細的瞭解。不然看直接看ConcurrentHashMap的原始碼還是有些費勁的。 相信對HashMap,HashTable有一定了解,應該知道
ConcurrentHashMap原始碼解析-Java7
目錄 一.ConcurrentHashMap的模型圖 二.原始碼分析-類定義 2.1 極簡ConcurrentHashMap定義 2.2 Segment內部類 2.3 HashEntry內部類 2.4 ConcurrentHashMap的重要常量 三.常用介面原始碼分析
ConcurrentHashMap原始碼解析,多執行緒擴容
前面一篇已經介紹過了 HashMap 的原始碼: [HashMap原始碼解析、jdk7和8之後的區別、相關問題分析](https://www.cnblogs.com/lifegoeson/p/13628737.html) HashMap並不是執行緒安全的,他就一個普通的容器,沒有做相關的同步處理,因此執行
Java併發包原始碼學習系列:JDK1.8的ConcurrentHashMap原始碼解析
[toc] 系列傳送門: - [Java併發包原始碼學習系列:AbstractQueuedSynchronizer](https://blog.csdn.net/Sky_QiaoBa_Sum/article/details/112254373) - [Java併發包原始碼學習系列:CLH同步佇列及同步資源
ConcurrentHashMap實現原理以及原始碼解析
ConcurrentHashMap實現原理以及原始碼解析 ConcurrentHashMap是Java1.5中引用的一個執行緒安全的支援高併發的HashMap集合類。 1、執行緒不安全的HashMap 因為多執行緒環境下,使用Hashmap進行put操作會引起死迴圈
【搞定Java8新特性】之Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析
本文轉載自:https://blog.csdn.net/a724888/article/details/68936953 本文目錄: 1、Java7 中的HashMap 1.1、put過程分析 陣列初始化 計算具體陣列位置 新增節點到連結串列中 陣列擴容 補充:Has
HashMap和ConcurrentHashMap 原始碼關鍵點解析
第一部分:關鍵原始碼講解 1.HashMap 是如何儲存的? a.底層是一個數組 tab b. hash=hash(key) ,然後根據陣列長度n和hash值,決定當前需要put的元素對應的陣列下標, hash演算法見紅框。
HashMap原始碼解析、ConcurrentHashMap、ConcurrentSkipListMap 初步熟悉 (JDK1.7之一)
兜兜轉轉開發java有些時間,可是每次都是用到採取學習,用過又回到原點,反反覆覆,究其緣由還是沒有合理的整理總結,只是個不斷重複搬磚、運磚的中高階工種,沒有不可替代的核心競爭力。 why?因為你30min——1h能完成的東西,不熟悉的人多花點時間也是可以完成的,