摘要:最近準(zhǔn)備面試,一談到基礎(chǔ),大部分面試官上來(lái)就數(shù)據(jù)結(jié)構(gòu)素質(zhì)三連與區(qū)別,底層數(shù)據(jù)結(jié)構(gòu),為什么能保證線程安全。數(shù)組順序存儲(chǔ),內(nèi)存連續(xù),查詢快,插入刪除效率稍微低,不過(guò)現(xiàn)在略有改善。而在開(kāi)始,是由和的方式去實(shí)現(xiàn)高并發(fā)下的線程安全。
最近準(zhǔn)備面試,一談到j(luò)ava基礎(chǔ),大部分面試官上來(lái)就java數(shù)據(jù)結(jié)構(gòu)素質(zhì)三連:ArrayList與LinkedList區(qū)別,HashMap底層數(shù)據(jù)結(jié)構(gòu),ConcurrentHashMap為什么能保證線程安全。
剛畢業(yè)的應(yīng)屆生,或者基礎(chǔ)不好程序員(比如:本尊,對(duì)沒(méi)錯(cuò)就是我~),只了解皮毛,一稍微深入就gg思密達(dá)。面試官:嗯...回頭等通知吧~ 基本一首《涼涼》送我到門(mén)外了。
不好意思,扯遠(yuǎn)了! 前兩個(gè)問(wèn)題很簡(jiǎn)單,一個(gè)數(shù)組一個(gè)鏈表。
數(shù)組順序存儲(chǔ),內(nèi)存連續(xù),查詢快,插入刪除效率稍微低(System.copyArray),不過(guò)現(xiàn)在略有改善。
鏈表插入刪除快速高效,查詢效率差了點(diǎn)意思,存儲(chǔ)不連續(xù)。
總之,各有利弊吧,根據(jù)業(yè)務(wù)場(chǎng)景選擇適合自己的存儲(chǔ)結(jié)構(gòu),不過(guò)現(xiàn)在也出現(xiàn)很多類(lèi)似的改進(jìn)版本,暫時(shí)不談了(其實(shí)我也沒(méi)了解過(guò),啊哈哈哈~有點(diǎn)尷尬)
HashMap JDK1.8以前基本都是數(shù)組+鏈表實(shí)現(xiàn),JDK1.8開(kāi)始改為數(shù)組+列表,當(dāng)列表長(zhǎng)度大于某個(gè)值(具體忘了),鏈表轉(zhuǎn)化為一個(gè)X爆了的數(shù)據(jù)結(jié)構(gòu)————紅黑樹(shù)(我都嚇尿了反正,看了幾百遍沒(méi)記住這玩意各種算法)
其實(shí)今天主要是想聊一下這個(gè)叫做ConcurrentHashMap的數(shù)據(jù)結(jié)構(gòu),看過(guò)網(wǎng)上幾篇文章實(shí)在是看的蛋疼,一來(lái)寫(xiě)的一般,對(duì)于源碼的復(fù)制粘貼,最為我看起來(lái)吃力;二來(lái)紅黑樹(shù)太難,看著難受的一比。是在無(wú)法理解這個(gè)數(shù)據(jù)結(jié)構(gòu)的精髓所在,故而想自己寫(xiě)篇文章來(lái)記錄自己學(xué)習(xí)的過(guò)程,就好比孫悟空去了一趟五指山下,做個(gè)標(biāo)記!
廢話少說(shuō)直接先上jb:
如圖所示,相比傳統(tǒng)HashMap,jdk1.8之前 ConcurrentHashMap 在傳統(tǒng)HashEntry之前增加了一個(gè)segment數(shù)組。Segment數(shù)組的意義就是將一個(gè)大的table分割成多個(gè)小的table來(lái)進(jìn)行加鎖,Segment數(shù)組中每一個(gè)元素就是一把鎖,每一個(gè)Segment元素存儲(chǔ)的是HashEntry數(shù)組+鏈表。而在jdk1.8開(kāi)始,ConcurrentHashMap是由CAS和Synchronized的方式去實(shí)現(xiàn)高并發(fā)下的線程安全。
我們主要從的get,put等方法來(lái)學(xué)習(xí)ConcurrentHashMap,是如何插入和獲取元素,以及如何保證線程安全。
先看下get方法源碼:
public V get(Object key) { Node[] tab; Node 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) 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; }
我看上面的代碼好多中間變量,很影響我這種菜鳥(niǎo)分析邏輯,于是我按照自己的編碼風(fēng)格,重寫(xiě)了一下:
public V get(Object key) { int h = (key.hashCode() ^ (key.hashCode() >>> 16)) & 0x7fffffff;// 2 ^31 -1 Node[] tab = table; // 一般對(duì)哈希表的散列很自然地會(huì)想到用hash值對(duì)length取模(即除法散列法) // Hashtable中也是這樣實(shí)現(xiàn)的,這種方法基本能保證元素在哈希表中散列的比較均勻, // 但取模會(huì)用到除法運(yùn)算,效率很低,HashMap中則通過(guò)h&(length-1)的方法來(lái)代替取模, // 同樣實(shí)現(xiàn)了均勻的散列,但效率要高很多,這也是HashMap對(duì)Hashtable的一個(gè)改進(jìn)。 Node e = tabAt(tab, (tab.length - 1) & h); if (tab == null || tab.length <= 0 ||e == null) { return null; } if (e.hash == h) { if (e.getKey() == key || (e.getKey() != null && key.equals(e.getKey()))){ return e.getValue(); } } else if (e.hash < 0) { Node p = e.find(h, key); return p!= null ? p.getValue() : null; } e = e.next; while (e != null) { if (e.hash != h) { return null; } if (e.getKey() == key || (e.getKey() != null && key.equals(e.getKey()))) return e.getValue(); } return null; }
int h = (key.hashCode() ^ (key.hashCode() >>> 16)) & 0x7fffffff;// 2 ^31 -1
代碼的意思————通過(guò)哈希值二進(jìn)制異或該哈希值二進(jìn)制右移動(dòng)16位 是為了計(jì)算哈希值 再和 上面那玩意進(jìn)行與運(yùn)算并不知道是什么鬼。如下圖:
計(jì)算出Hash值之后要通過(guò)hash值找到對(duì)應(yīng)數(shù)組的下標(biāo)進(jìn)而找到數(shù)組元素:
Nodee = tabAt(tab, (tab.length - 1) & h);
(tab.length - 1) & h 根據(jù)計(jì)算出來(lái)的hash值從HashMap的“骨干”——bucket數(shù)組找到對(duì)應(yīng)的bucket
java.util.HashMap (ConcurrentHashMap同樣)保證bucket數(shù)組的長(zhǎng)度是2的冪方,所以本來(lái)應(yīng)該寫(xiě)成:
index = n % length的,變?yōu)榭梢詫?xiě)成:index = n & (length - 1) ,“&”效率會(huì)高一點(diǎn)。
說(shuō)了這么多我們來(lái)看下tabAt方法:
public static int numberOfLeadingZeros(int i) { // HD, Figure 5-6 if (i == 0) return 32; int n = 1; if (i >>> 16 == 0) { n += 16; i <<= 16; } if (i >>> 24 == 0) { n += 8; i <<= 8; } if (i >>> 28 == 0) { n += 4; i <<= 4; } if (i >>> 30 == 0) { n += 2; i <<= 2; } n -= i >>> 31; return n; } U = sun.misc.Unsafe.getUnsafe(); // 獲取unsafe類(lèi)的實(shí)例 單例模式 @CallerSensitive public static Unsafe getUnsafe() { Class arg = Reflection.getCallerClass();//獲取調(diào)用者方法的類(lèi) if (!VM.isSystemDomainLoader(arg.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } } Class> ak = Node[].class; ABASE = U.arrayBaseOffset(ak); int scale = U.arrayIndexScale(ak); ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); @SuppressWarnings("unchecked") static finalNode tabAt(Node [] tab, int i) { return (Node )U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76858.html
摘要:一算法問(wèn)題將一群猴子排成一圈,按照猴子數(shù)按照依次編號(hào)。在計(jì)算機(jī)編程的算法中,類(lèi)似問(wèn)題又稱(chēng)為約瑟夫環(huán)。問(wèn)題是,給定了和,一開(kāi)始要站在什么地方才能避免被處決四算法設(shè)計(jì)與實(shí)現(xiàn)算法問(wèn)題存猴子名稱(chēng)給猴子定義名稱(chēng)定義猴子排序個(gè)數(shù) 一、算法問(wèn)題 將一群猴子排成一圈,按照猴子數(shù)按照1,2,...,n依次編號(hào)。然后從第1只開(kāi)始數(shù),定義數(shù)m個(gè)猴子,之后將數(shù)到的猴子將它踢出圈,從它后面再開(kāi)始數(shù), 再數(shù)到第m...
摘要:行結(jié)束符之后的符號(hào)有二義性,使得該符號(hào)與上條語(yǔ)句能夠無(wú)縫對(duì)接,不導(dǎo)致語(yǔ)法錯(cuò)誤。然而在中,有幾種特殊語(yǔ)句是不允許行結(jié)束符存在的。如果語(yǔ)句中有行結(jié)束符,會(huì)優(yōu)先認(rèn)為行結(jié)束符表示的是語(yǔ)句的結(jié)束,這在標(biāo)準(zhǔn)中稱(chēng)為限制產(chǎn)生式。 showImg(https://segmentfault.com/img/bVmyZB); 什么是 ASI ? 自動(dòng)分號(hào)插入 (automatic semicolon i...
摘要:是線程安全,性能出色的的線程安全實(shí)現(xiàn),相比較他是線程安全的,相比較他的性能優(yōu)勢(shì)非常明顯。的源碼其實(shí)很簡(jiǎn)單,和的結(jié)構(gòu)一致,但是每一個(gè)方法都是用來(lái)修飾,以保證操作是線程安全的。這樣在多線程的情況下,只有一個(gè)線程獲取鎖操作中的數(shù)據(jù)。 ConcurrentHashMap ConcurrentHashMap是線程安全,性能出色的Map的線程安全實(shí)現(xiàn),相比較HashMap他是線程安全的,相比較Ha...
摘要:如下代碼省略相關(guān)代碼省略相關(guān)代碼可以看到在里面,是直接采用數(shù)組鏈表紅黑樹(shù)來(lái)實(shí)現(xiàn),時(shí)間復(fù)雜度在和之間,如果鏈表轉(zhuǎn)化為紅黑樹(shù)了,那么就是到。 在JDK1.8里面,ConcurrentHashMap在put方法里面已經(jīng)將分段鎖移除了,轉(zhuǎn)而是CAS鎖和synchronized ConcurrentHashMap是Java里面同時(shí)兼顧性能和線程安全的一個(gè)鍵值對(duì)集合,同屬于鍵值對(duì)的集合還有Hash...
摘要:與中的類(lèi)似,也是一個(gè)數(shù)組加鏈表,不過(guò)這個(gè)線程安全。線程安全,但是它的線程安全是依賴將所有修改的代碼塊都用修飾。這是中實(shí)現(xiàn)線程安全的思路,由個(gè)組成,每個(gè)就相當(dāng)于一個(gè)數(shù)組鏈表。線程安全,但性能差,不推薦使用。 問(wèn)題描述 翻翻別人的面試經(jīng)歷 這里在知乎上看到的,分享出了自己面試阿里Java崗的面試題。 showImg(https://segmentfault.com/img/bVbfSZ5?...
閱讀 2327·2021-11-23 09:51
閱讀 3760·2021-11-11 10:57
閱讀 1406·2021-10-09 09:43
閱讀 2496·2021-09-29 09:35
閱讀 2026·2019-08-30 15:54
閱讀 1796·2019-08-30 15:44
閱讀 3190·2019-08-30 13:20
閱讀 1700·2019-08-30 11:19