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