摘要:如下代碼省略相關(guān)代碼省略相關(guān)代碼可以看到在里面,是直接采用數(shù)組鏈表紅黑樹來實現(xiàn),時間復(fù)雜度在和之間,如果鏈表轉(zhuǎn)化為紅黑樹了,那么就是到。
在JDK1.8里面,ConcurrentHashMap在put方法里面已經(jīng)將分段鎖移除了,轉(zhuǎn)而是CAS鎖和synchronized
ConcurrentHashMap是Java里面同時兼顧性能和線程安全的一個鍵值對集合,同屬于鍵值對的集合還有HashTable以及HashMap,
HashTable是一個線程安全的類,因為它的所有public方法都被synchronized修飾,這樣就導(dǎo)致了一個問題,就是效率太低。
雖然HashMap在JDK1.8的并發(fā)場景下觸發(fā)擴(kuò)容時不會出現(xiàn)成環(huán)了,但是會出現(xiàn)數(shù)據(jù)丟失的情況。
所以如果需要在多線程的情況下(多讀少寫))使用Map集合的話,ConcurrentHashMap是一個不錯的選擇。
ConcurrentHashMap在JDK1.8的時候?qū)ut()方法中的分段鎖Segment移除,轉(zhuǎn)而采用一種CAS鎖和synchronized來實現(xiàn)插入方法的線程安全。
如下代碼:
/** Implementation for put and putIfAbsent */ final V putVal(K key, V value, boolean onlyIfAbsent) { //省略相關(guān)代碼 for (Node[] tab = table;;) { Node f; int n, i, fh; 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 (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) { //省略相關(guān)代碼 } if (binCount != 0) { if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } addCount(1L, binCount); return null; }
可以看到在JDK1.8里面,ConcurrentHashMap是直接采用數(shù)組+鏈表+紅黑樹來實現(xiàn),時間復(fù)雜度在O(1)和O(n)之間,如果鏈表轉(zhuǎn)化為紅黑樹了,那么就是O(1)到O(nlogn)。
在這里值得一提的是,ConcurrentHashMap會判斷tabAt(tab, i = (n - 1) & hash)是不是 null,是的話就直接采用CAS進(jìn)行插入,而如果不為空的話,則是synchronized鎖住當(dāng)前Node的首節(jié)點(diǎn),這是因為當(dāng)該Node不為空的時候,證明了此時出現(xiàn)了Hash碰撞,就會涉及到鏈表的尾節(jié)點(diǎn)新增或者紅黑樹的節(jié)點(diǎn)新增以及紅黑樹的平衡,這些操作自然都是非原子性的。
從而導(dǎo)致無法使用CAS,當(dāng)Node的當(dāng)前下標(biāo)為null的時候,由于只是涉及數(shù)組的新增,所以用CAS即可。
因為CAS是一種基于版本控制的方式來實現(xiàn),而碰撞之后的操作太多,所以直接用synchronized比較合適。ConcurrentHashMap在迭代時和HashMap的區(qū)別
當(dāng)一個集合在迭代的時候如果動態(tài)的添加或者刪除元素,那么就會拋出Concurrentmodificationexception,但是在ConcurrentHashMap里面卻不會,例如如下代碼:
public static void main(String[] args) { Mapmap = new ConcurrentHashMap (); map.put("a","a1"); map.put("b","b1"); map.put("c","c1"); map.put("d","d1"); map.put("e","e1"); Iterator iterator = map.keySet().iterator(); while (iterator.hasNext()){ String it = iterator.next(); if("b".equals(it)){ map.remove("d"); } System.out.println(it); } } 控制臺打印如下: a b c e
而當(dāng)你把ConcurrentHashMap換成HashMap的時候,控制臺就會拋出一個異常:
Exception in thread "main" a b java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextNode(HashMap.java:1442) at java.util.HashMap$KeyIterator.next(HashMap.java:1466) at xyz.somersames.ListTest.main(ListTest.java:22)
原因在于ConcurrentHashMap的next方法并不會去檢查modCount和expectedModCount,但是會檢查下一個節(jié)點(diǎn)是不是為空
if ((p = next) == null) throw new NoSuchElementException();
當(dāng)我們進(jìn)行remove的時候,ConcurrentHashMap會直接通過修改指針的方式來進(jìn)行移除操作,同樣的,也會鎖住數(shù)組的頭節(jié)點(diǎn)直至移除結(jié)束,所以在同一個時刻,只會有一個線程對當(dāng)前數(shù)組下標(biāo)的所有節(jié)點(diǎn)進(jìn)行操作。
但是在HashMap里面,next方法會進(jìn)行一個check,而remove操作會修改modCount,導(dǎo)致modCount和expectedModCount不相等,所以就會導(dǎo)致
ConcurrentModificationException
稍微修改下代碼:
public static void main(String[] args) { Map并發(fā)下的處理map = new ConcurrentHashMap (); map.put("a","a1"); map.put("b","b1"); map.put("c","c1"); map.put("d","d1"); map.put("e","e1"); Iterator iterator = map.keySet().iterator(); while (iterator.hasNext()){ if("b".equals(iterator.next())){ map.remove("d"); } System.out.println(iterator.next()); } } 控制臺打印如下: b d Exception in thread "main" java.util.NoSuchElementException at java.util.concurrent.ConcurrentHashMap$KeyIterator.next(ConcurrentHashMap.java:3416) at com.xzh.ssmtest.ListTest.main(ListTest.java:25)
由于每一個Node的首節(jié)點(diǎn)都會被synchronized修飾,從而將一個元素的新增轉(zhuǎn)化為一個原子操作,同時Node的value和next都是由volatile關(guān)鍵字進(jìn)行修飾,從而可以保證可見性。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/74650.html
摘要:下面我來簡單總結(jié)一下的核心要點(diǎn)底層結(jié)構(gòu)是散列表數(shù)組鏈表紅黑樹,這一點(diǎn)和是一樣的。是將所有的方法進(jìn)行同步,效率低下。而作為一個高并發(fā)的容器,它是通過部分鎖定算法來進(jìn)行實現(xiàn)線程安全的。 前言 聲明,本文用的是jdk1.8 前面章節(jié)回顧: Collection總覽 List集合就這么簡單【源碼剖析】 Map集合、散列表、紅黑樹介紹 HashMap就是這么簡單【源碼剖析】 LinkedHas...
摘要:最近準(zhǔn)備面試,一談到基礎(chǔ),大部分面試官上來就數(shù)據(jù)結(jié)構(gòu)素質(zhì)三連與區(qū)別,底層數(shù)據(jù)結(jié)構(gòu),為什么能保證線程安全。數(shù)組順序存儲,內(nèi)存連續(xù),查詢快,插入刪除效率稍微低,不過現(xiàn)在略有改善。而在開始,是由和的方式去實現(xiàn)高并發(fā)下的線程安全。 最近準(zhǔn)備面試,一談到j(luò)ava基礎(chǔ),大部分面試官上來就java數(shù)據(jù)結(jié)構(gòu)素質(zhì)三連:ArrayList與LinkedList區(qū)別,HashMap底層數(shù)據(jù)結(jié)構(gòu),Concur...
摘要:若遇到哈希沖突,則將沖突的值加到鏈表中即可。之后相比于之前的版本,之后在解決哈希沖突時有了較大的變化,當(dāng)鏈表長度大于閾值默認(rèn)為時,將鏈表轉(zhuǎn)化為紅黑樹,以減少搜索時間。有序,唯一紅黑樹自平衡的排序二叉樹。 本文是最最最常見Java面試題總結(jié)系列第三周的文章。主要內(nèi)容: Arraylist 與 LinkedList 異同 ArrayList 與 Vector 區(qū)別 HashMap的底層...
ConcurrentHashMap源碼分析_JDK1.8版本 聲明 文章均為本人技術(shù)筆記,轉(zhuǎn)載請注明出處[1] https://segmentfault.com/u/yzwall[2] blog.csdn.net/j_dark/ JDK1.6版本 ConcurrentHashMap結(jié)構(gòu) showImg(https://segmentfault.com/img/remote/146000000900...
閱讀 2535·2023-04-26 02:57
閱讀 1417·2023-04-25 21:40
閱讀 2188·2021-11-24 09:39
閱讀 3568·2021-08-30 09:49
閱讀 772·2019-08-30 15:54
閱讀 1178·2019-08-30 15:52
閱讀 2092·2019-08-30 15:44
閱讀 1282·2019-08-28 18:27