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