Java中的concurrenthashmap集合詳細(xì)剖析
concurrenthashmap
有參構(gòu)造后第一次put時(shí)會(huì)進(jìn)行初始化。初始化的容量并不是所傳入的數(shù)。
源碼:
public ConcurrentHashMap(int initialCapacity) { if (initialCapacity < 0) throw new IllegalArgumentException(); int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); this.sizeCtl = cap; }
由源碼可知,會(huì)先判斷所傳入的容量是否>=最大容量的一半,如果滿足條件,就會(huì)將容量修改為最大值,反之則會(huì)將容量改為所傳入數(shù)*1.5+1。
并且這個(gè)tableSizeFor函數(shù)會(huì)將這個(gè)數(shù)修改為2**n>initialCapacity + (initialCapacity >>> 1) + 1的最小2**n。
舉個(gè)例子證明一下:
ConcurrentHashMap<String,String> a = new ConcurrentHashMap<>(32); Class<? extends ConcurrentHashMap> aClass = a.getClass(); Field sizeCtl = aClass.getDeclaredField("sizeCtl"); sizeCtl.setAccessible(true); System.out.println(sizeCtl.getName()+":"+sizeCtl.get(a));
輸出如下:
sizeCtl:64
這里的sizeCtl有四種情況。
類型 | 含義 |
sizeCtl為0 | 代表此時(shí)還未進(jìn)行初始化操作。 |
sizeCtl為正數(shù) | 若還未初始化則代表的是初始容量,已經(jīng)初始化則代表的是擴(kuò)容閾值 |
sizeCtl為-1 | 代表正在進(jìn)行初始化操作 |
sizeCtl為負(fù)數(shù)不為-1時(shí) | 代表此時(shí)有-1*(1+n)個(gè)線程正在進(jìn)行擴(kuò)容操作 |
當(dāng)?shù)谝淮螌懭霐?shù)據(jù)時(shí):
源碼如下:
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) {//onlyIfAbsent是當(dāng)存在相同元素時(shí)是否覆蓋。這里傳入了false就是覆蓋的意思。 if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode());//計(jì)算key對象的hash值 int binCount = 0; for (Node<K,V>[] tab = table;;) {//這里是一個(gè)死循環(huán)。 Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0)//如果table還未進(jìn)行初始化操作,在第一次寫入數(shù)據(jù)時(shí)會(huì)進(jìn)行初始化。 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 } else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else { V oldVal = null; 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; 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; }
for (Node<K,V>[] tab = table;;) {//這里是一個(gè)死循環(huán)。這個(gè)循環(huán)中存在4個(gè)判斷 分別是: 1、if (tab == null || (n = tab.length) == 0)//代表還未初始化或者當(dāng)前長度為0。 2、else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//根據(jù)key的hash值來判斷將要放入哪個(gè)位置,并將f指向這個(gè)位置。 如果這個(gè)位置目前還不存在數(shù)據(jù)的話。就會(huì)進(jìn)入 if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; 語句。這里存在一個(gè)cas自旋。并且會(huì)進(jìn)行雙重檢測,一旦發(fā)現(xiàn)符合條件,就會(huì)將要添加的節(jié)點(diǎn)放入f指向的位置,并跳出循環(huán),結(jié)束。 3、else if ((fh = f.hash) == MOVED)//MOVED為-1,表示當(dāng)前entry已經(jīng)遷移,里面的tab = helpTransfer(tab, f);表示協(xié)助遷移。遷移完成后或再次循環(huán)并進(jìn)行判斷。 4、else中有一把瑣synchronized (f) 。在上面的代碼中i已經(jīng)被賦值為(n - 1) & hash)。 這把鎖所包括的代碼塊中 存在雙重檢測代碼if (tabAt(tab, i) == f)。 他的存在就是為了檢測曾經(jīng)獲得的桶的頭節(jié)點(diǎn)的位置是否已經(jīng)修改。 因?yàn)樵趍ap中存在樹-->鏈表和鏈表->樹的操作,這回改變桶頭節(jié)點(diǎn)的位置。 但是這并不能保證安全, 因?yàn)樵谵D(zhuǎn)化后頭節(jié)點(diǎn)的位置可能不會(huì)發(fā)生改變,所以還存在if (fh >= 0)這一句代碼, 表示f這個(gè)節(jié)點(diǎn)的hash。 樹節(jié)點(diǎn)的hash時(shí)=是負(fù)數(shù)。 ok,如果未修改的話他會(huì)遍歷桶下面的節(jié)點(diǎn),判斷是否存在相同的節(jié)點(diǎn)。如果存在的話, 就覆蓋,不存在就加到鏈表尾部。 在添加完成后執(zhí)行: if (binCount != 0) { if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } 這部分代碼表示是否進(jìn)行樹化。如果當(dāng)前map的長度不小于64,那么當(dāng)一個(gè)桶中的元素大于等于8的時(shí)候,會(huì)進(jìn)行 樹化。 最后執(zhí)行addCount(1L, binCount); 這個(gè)代碼很復(fù)雜??梢詤⒖糷ttps://www.bilibili.com/video/BV17i4y1x71z?from=search&seid=2906009396999368521&spm_id_from=333.337.0.0
初始化源碼:
private final Node<K,V>[] initTable() { Node<K,V>[] tab; int sc; while ((tab = table) == null || tab.length == 0) { if ((sc = sizeCtl) < 0) Thread.yield(); // lost initialization race; just spin else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { if ((tab = table) == null || tab.length == 0) { int n = (sc > 0) ? sc : DEFAULT_CAPACITY; @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; table = tab = nt; sc = n - (n >>> 2); } } finally { sizeCtl = sc; } break; } } return tab; }
我的理解是在嗎map初始化之后,map的長度縮減到0的話,會(huì)重新進(jìn)行一次初始化。
在并發(fā)環(huán)境下初始化的時(shí)候有可能會(huì)有多個(gè)線程進(jìn)入此方法。
不過這里存在一個(gè)cas自旋鎖U.compareAndSwapInt(this, SIZECTL, sc, -1),首先,當(dāng)未初始化的時(shí)候sizeCtl為0,所以會(huì)進(jìn)入else if??赡軙?huì)有多個(gè)線程符合標(biāo)準(zhǔn)進(jìn)入else if,但是這里存在一個(gè)cas自旋鎖,當(dāng)?shù)谝粋€(gè)觸碰到cas的線程進(jìn)行操作后,sizeCtl就會(huì)被修改為-1,如果修改成功則返回true,當(dāng)另一個(gè)線程進(jìn)入自旋鎖后會(huì)發(fā)現(xiàn)sizeCtl, sc并不相等了,所以會(huì)返回false,離開else if,這時(shí)再吃循環(huán)到if語句時(shí)會(huì)發(fā)現(xiàn)sizeCtl=-1,然后進(jìn)入if,執(zhí)行Thread.yield(),謙讓出cpu。
直到初始化完成sizeCtl為擴(kuò)容閾值為止。此時(shí)sizeCtl已經(jīng)不滿足if語句了,所以那些還在謙讓的線程就會(huì)開始陸陸續(xù)續(xù)的執(zhí)行else if語句,發(fā)現(xiàn)cas返回true,然后再else if中存在雙重檢測,判斷table是否為null。很顯然并不滿足。
于是執(zhí)行finally中的語句,最后跳出循環(huán),返回table。
到此這篇關(guān)于Java中的concurrenthashmap集合詳細(xì)剖析的文章就介紹到這了,更多相關(guān)concurrenthashmap集合內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java線程的調(diào)度與優(yōu)先級(jí)詳解
這篇文章主要為大家詳細(xì)介紹了Java線程的調(diào)度與優(yōu)先級(jí),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03Java實(shí)踐練習(xí)輕松幾行實(shí)現(xiàn)追書神器
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用Java實(shí)現(xiàn)一個(gè)追書神器,用技術(shù)改變生活,大家可以在過程中查缺補(bǔ)漏,提升水平2021-10-10Spring Data JPA中的Specification動(dòng)態(tài)查詢詳解
Specification是一個(gè)設(shè)計(jì)模式,用于企業(yè)級(jí)應(yīng)用開發(fā)中,其主要目的是將業(yè)務(wù)規(guī)則從業(yè)務(wù)邏輯中分離出來,在數(shù)據(jù)查詢方面,Specification可以定義復(fù)雜的查詢,使其更易于重用和測試,這篇文章主要介紹了Spring Data JPA中的Specification動(dòng)態(tài)查詢詳解,需要的朋友可以參考下2023-07-07Java集合操作之List接口及其實(shí)現(xiàn)方法詳解
這篇文章主要介紹了Java集合操作之List接口及其實(shí)現(xiàn)方法,詳細(xì)分析了Java集合操作中List接口原理、功能、用法及操作注意事項(xiàng),需要的朋友可以參考下2015-07-07Spring?Cloud?中使用?Sentinel?實(shí)現(xiàn)服務(wù)限流的兩種方式
這篇文章主要介紹了Spring?Cloud?中使用?Sentinel?實(shí)現(xiàn)服務(wù)限流的方式,通過示例代碼主要介紹了Sentinel的兩種實(shí)現(xiàn)限流的方式,需要的朋友可以參考下2024-03-03詳解SpringBoot可執(zhí)行Jar包運(yùn)行原理
SpringBoot有一個(gè)很方便的功能就是可以將應(yīng)用打成可執(zhí)行的Jar,那么大家有沒想過這個(gè)Jar是怎么運(yùn)行起來的呢,本篇博客就來介紹下 SpringBoot可執(zhí)行Jar包的運(yùn)行原理,需要的朋友可以參考下2023-05-05Kafka Java Producer代碼實(shí)例詳解
這篇文章主要介紹了Kafka Java Producer代碼實(shí)例詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06