你知道的越多,你不知道的越多
點(diǎn)贊再看土铺,養(yǎng)成習(xí)慣
本文 GitHub https://github.com/JavaFamily 已收錄,有一線大廠面試點(diǎn)思維導(dǎo)圖,也整理了很多我的文檔理郑,歡迎Star和完善,大家面試可以參照考點(diǎn)復(fù)習(xí)咨油,希望我們一起有點(diǎn)東西您炉。
前言
作為一個(gè)在互聯(lián)網(wǎng)公司面一次拿一次Offer的面霸,打敗了無數(shù)競(jìng)爭(zhēng)對(duì)手臼勉,每次都只能看到無數(shù)落寞的身影失望的離開邻吭,略感愧疚(請(qǐng)?jiān)试S我使用一下夸張的修辭手法)。
于是在一個(gè)寂寞難耐的夜晚宴霸,我痛定思痛囱晴,決定開始寫互聯(lián)網(wǎng)技術(shù)棧面試相關(guān)的文章,希望能幫助各位讀者以后面試勢(shì)如破竹瓢谢,對(duì)面試官進(jìn)行360°的反擊畸写,吊打問你的面試官,讓一同面試的同僚瞠目結(jié)舌氓扛,瘋狂收割大廠Offer枯芬!
所有文章的名字只是我的噱頭,我們應(yīng)該有一顆謙遜的心采郎,所以希望大家懷著空杯心態(tài)好好學(xué)千所,一起進(jìn)步。
回手掏
上次面試呀蒜埋,我發(fā)現(xiàn)面試官對(duì)我的幾個(gè)回答還是不夠滿意淫痰,覺得還是有點(diǎn)疑問,我就挑幾個(gè)回答一下整份。
16是2的冪待错,8也是籽孙,32也是,為啥偏偏選了16火俄?
我覺得就是一個(gè)經(jīng)驗(yàn)值犯建,定義16沒有很特別的原因,只要是2次冪瓜客,其實(shí)用 8 和 32 都差不多适瓦。
用16只是因?yàn)樽髡哒J(rèn)為16這個(gè)初始容量是能符合常用而已。
Hashmap中的鏈表大小超過八個(gè)時(shí)會(huì)自動(dòng)轉(zhuǎn)化為紅黑樹忆家,當(dāng)刪除小于六時(shí)重新變?yōu)殒湵碛坦剑瑸樯赌兀?/p>
根據(jù)泊松分布,在負(fù)載因子默認(rèn)為0.75的時(shí)候芽卿,單個(gè)hash槽內(nèi)元素個(gè)數(shù)為8的概率小于百萬分之一揭芍,所以將7作為一個(gè)分水嶺,等于7的時(shí)候不轉(zhuǎn)換卸例,大于等于8的時(shí)候才進(jìn)行轉(zhuǎn)換称杨,小于等于6的時(shí)候就化為鏈表。
正文
一個(gè)婀娜多姿筷转,穿著襯衣的小姐姐姑原,拿著一個(gè)精致的小筆記本,徑直走過來坐在我的面前呜舒。
就在我口水要都要流出來的時(shí)候锭汛,小姐姐的話語打斷了我的YY。
喂小鬼袭蝗,你養(yǎng)我盎脚埂!
呸呸呸到腥,說錯(cuò)了朵逝,上次的HashMap回答得不錯(cuò),最后因?yàn)樘焐砹嗣嬖嚥莶菔請(qǐng)鱿绶叮@次可得好好安排你配名。
誒,面試官上次是在抱歉晋辆,因?yàn)楣倦p十二要值班渠脉,實(shí)在是沒辦法,不過這次不會(huì)了瓶佳,我推掉了所有的事情準(zhǔn)備全身心投入到今天的面試中连舍,甚至推掉了隔壁王大爺?shù)募s會(huì)邀約。
這樣最好涩哟,上次我們最后聊到HashMap在多線程環(huán)境下存在線程安全問題索赏,那你一般都是怎么處理這種情況的?
美麗迷人的面試官您好颗祝,一般在多線程的場(chǎng)景萌庆,我都會(huì)使用好幾種不同的方式去代替:
- 使用Collections.synchronizedMap(Map)創(chuàng)建線程安全的map集合遂铡;
- Hashtable
- ConcurrentHashMap
不過出于線程并發(fā)度的原因,我都會(huì)舍棄前兩者使用最后的ConcurrentHashMap融涣,他的性能和效率明顯高于前兩者。
哦精钮,Collections.synchronizedMap是怎么實(shí)現(xiàn)線程安全的你有了解過么威鹿?
臥!不按照套路出牌呀*轨香,正常不都是問HashMap和ConcurrentHashMap么忽你,這次怎么問了這個(gè)鬼東西,還好我飽讀詩書臂容,經(jīng)晨砌ǎ看敖丙的《吊打面試官》系列,不然真的完了脓杉。
小姐姐您這個(gè)問題真好糟秘,別的面試官都沒問過,說真的您水平肯定是頂級(jí)技術(shù)專家吧球散。
別貧嘴尿赚,快回答我的問題!抿嘴一笑??
在SynchronizedMap內(nèi)部維護(hù)了一個(gè)普通對(duì)象Map蕉堰,還有排斥鎖mutex凌净,如圖
Collections.synchronizedMap(new HashMap<>(16));
我們?cè)谡{(diào)用這個(gè)方法的時(shí)候就需要傳入一個(gè)Map,可以看到有兩個(gè)構(gòu)造器嘁灯,如果你傳入了mutex參數(shù)泻蚊,則將對(duì)象排斥鎖賦值為傳入的對(duì)象。
如果沒有丑婿,則將對(duì)象排斥鎖賦值為this性雄,即調(diào)用synchronizedMap的對(duì)象,就是上面的Map羹奉。
創(chuàng)建出synchronizedMap之后秒旋,再操作map的時(shí)候,就會(huì)對(duì)方法上鎖诀拭,如圖全是??
臥*迁筛,小伙子,秒啊耕挨,其實(shí)我早就忘了源碼了细卧,就是瞎問一下尉桩,沒想到還是回答上來了,接下來就面對(duì)疾風(fēng)吧贪庙。
回答得不錯(cuò)蜘犁,能跟我聊一下Hashtable么?
這個(gè)我就等著你問呢嘿嘿止邮!
跟HashMap相比Hashtable是線程安全的这橙,適合在多線程的情況下使用,但是效率可不太樂觀导披。
哦屈扎,你能說說他效率低的原因么?
嗯嗯面試官撩匕,我看過他的源碼鹰晨,他在對(duì)數(shù)據(jù)操作的時(shí)候都會(huì)上鎖,所以效率比較低下滑沧。
除了這個(gè)你還能說出一些Hashtable 跟HashMap不一樣點(diǎn)么并村?
!吶呢滓技?這叫什么問題嘛哩牍?這個(gè)又是知識(shí)盲區(qū)呀!
呃令漂,面試官我從來沒使用過他膝昆,你容我想想?yún)^(qū)別的點(diǎn),說完便開始抓頭發(fā)叠必,這次不是裝的荚孵,是真的!
Hashtable 是不允許鍵或值為 null 的纬朝,HashMap 的鍵值則都可以為 null收叶。
呃我能打斷你一下么?為啥 Hashtable 是不允許 KEY 和 VALUE 為 null, 而 HashMap 則可以呢共苛?
尼*判没,我這個(gè)時(shí)候怎么覺得面前的人不好看了,甚至像個(gè)魔鬼隅茎,看著對(duì)自己面試官心里想到澄峰。
因?yàn)镠ashtable在我們put 空值的時(shí)候會(huì)直接拋空指針異常,但是HashMap卻做了特殊處理辟犀。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
但是你還是沒說為啥Hashtable 是不允許鍵或值為 null 的俏竞,HashMap 的鍵值則都可以為 null?
這是因?yàn)镠ashtable使用的是安全失敗機(jī)制(fail-safe),這種機(jī)制會(huì)使你此次讀到的數(shù)據(jù)不一定是最新的數(shù)據(jù)魂毁。
如果你使用null值玻佩,就會(huì)使得其無法判斷對(duì)應(yīng)的key是不存在還是為空,因?yàn)槟銦o法再調(diào)用一次contain(key)來對(duì)key是否存在進(jìn)行判斷漱牵,ConcurrentHashMap同理夺蛇。
好的你繼續(xù)說不同點(diǎn)吧。
-
實(shí)現(xiàn)方式不同:Hashtable 繼承了 Dictionary類酣胀,而 HashMap 繼承的是 AbstractMap 類。
Dictionary 是 JDK 1.0 添加的娶聘,貌似沒人用過這個(gè)闻镶,我也沒用過。
初始化容量不同:HashMap 的初始容量為:16丸升,Hashtable 初始容量為:11铆农,兩者的負(fù)載因子默認(rèn)都是:0.75。
擴(kuò)容機(jī)制不同:當(dāng)現(xiàn)有容量大于總?cè)萘?* 負(fù)載因子時(shí)狡耻,HashMap 擴(kuò)容規(guī)則為當(dāng)前容量翻倍墩剖,Hashtable 擴(kuò)容規(guī)則為當(dāng)前容量翻倍 + 1。
-
迭代器不同:HashMap 中的 Iterator 迭代器是 fail-fast 的夷狰,而 Hashtable 的 Enumerator 不是 fail-fast 的岭皂。
所以,當(dāng)其他線程改變了HashMap 的結(jié)構(gòu)沼头,如:增加爷绘、刪除元素,將會(huì)拋出ConcurrentModificationException 異常进倍,而 Hashtable 則不會(huì)土至。
fail-fast是啥?
臥*猾昆,你自己不知道么陶因?為啥問我!4刮稀楷扬!還好我會(huì)!
快速失斆纯埂(fail—fast)是java集合中的一種機(jī)制毅否, 在用迭代器遍歷一個(gè)集合對(duì)象時(shí),如果遍歷過程中對(duì)集合對(duì)象的內(nèi)容進(jìn)行了修改(增加蝇刀、刪除螟加、修改),則會(huì)拋出Concurrent Modification Exception。
他的原理是啥捆探?
迭代器在遍歷時(shí)直接訪問集合中的內(nèi)容然爆,并且在遍歷過程中使用一個(gè) modCount 變量。
集合在被遍歷期間如果內(nèi)容發(fā)生變化黍图,就會(huì)改變modCount的值曾雕。
每當(dāng)?shù)魇褂胔ashNext()/next()遍歷下一個(gè)元素之前,都會(huì)檢測(cè)modCount變量是否為expectedmodCount值助被,是的話就返回遍歷剖张;否則拋出異常,終止遍歷揩环。
Tip:這里異常的拋出條件是檢測(cè)到 modCount搔弄!=expectedmodCount 這個(gè)條件。如果集合發(fā)生變化時(shí)修改modCount值剛好又設(shè)置為了expectedmodCount值丰滑,則異常不會(huì)拋出顾犹。
因此,不能依賴于這個(gè)異常是否拋出而進(jìn)行并發(fā)操作的編程褒墨,這個(gè)異常只建議用于檢測(cè)并發(fā)修改的bug炫刷。
說說他的場(chǎng)景?
java.util包下的集合類都是快速失敗的郁妈,不能在多線程下發(fā)生并發(fā)修改(迭代過程中被修改)算是一種安全機(jī)制吧浑玛。
Tip:安全失敗(fail—safe)大家也可以了解下圃庭,java.util.concurrent包下的容器都是安全失敗锄奢,可以在多線程下并發(fā)使用,并發(fā)修改剧腻。
嗯拘央?這個(gè)小鬼這么有東西的嘛?居然把不同點(diǎn)幾乎都說出來了书在,被人遺忘的Hashtable都能說得頭頭是道灰伟,看來不簡(jiǎn)單,不知道接下來的ConcurrentHashMap連環(huán)炮能不能頂?shù)米×恕?/p>
都說了他的并發(fā)度不夠儒旬,性能很低栏账,這個(gè)時(shí)候你都怎么處理的?
他來了他來了栈源,他終于還是來了挡爵,等了這么久,就是等你問我這個(gè)點(diǎn)甚垦,你還是掉入了我的陷阱啊茶鹃,我早有準(zhǔn)備涣雕,在HashMap埋下他線程不安全的種子,就是為了在ConcurrentHashMap開花結(jié)果闭翩!
小姐姐:這樣的場(chǎng)景挣郭,我們?cè)陂_發(fā)過程中都是使用ConcurrentHashMap,他的并發(fā)的相比前兩者好很多疗韵。
哦兑障?那你跟我說說他的數(shù)據(jù)結(jié)構(gòu)吧,以及為啥他并發(fā)度這么高蕉汪?
ConcurrentHashMap 底層是基于 數(shù)組 + 鏈表
組成的流译,不過在 jdk1.7 和 1.8 中具體實(shí)現(xiàn)稍有不同。
我先說一下他在1.7中的數(shù)據(jù)結(jié)構(gòu)吧:
如圖所示肤无,是由 Segment 數(shù)組先蒋、HashEntry 組成,和 HashMap 一樣宛渐,仍然是數(shù)組加鏈表。
Segment 是 ConcurrentHashMap 的一個(gè)內(nèi)部類眯搭,主要的組成如下:
static final class Segment<K,V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
// 和 HashMap 中的 HashEntry 作用一樣窥翩,真正存放數(shù)據(jù)的桶
transient volatile HashEntry<K,V>[] table;
transient int count;
// 記得快速失敗(fail—fast)么鳞仙?
transient int modCount;
// 大小
transient int threshold;
// 負(fù)載因子
final float loadFactor;
}
HashEntry跟HashMap差不多的寇蚊,但是不同點(diǎn)是,他使用volatile去修飾了他的數(shù)據(jù)Value還有下一個(gè)節(jié)點(diǎn)next棍好。
volatile的特性是啥仗岸?
保證了不同線程對(duì)這個(gè)變量進(jìn)行操作時(shí)的可見性,即一個(gè)線程修改了某個(gè)變量的值借笙,這新值對(duì)其他線程來說是立即可見的扒怖。(實(shí)現(xiàn)可見性)
禁止進(jìn)行指令重排序。(實(shí)現(xiàn)有序性)
volatile 只能保證對(duì)單次讀/寫的原子性业稼。i++ 這種操作不能保證原子性盗痒。
我就不大篇幅介紹了,多線程章節(jié)我會(huì)說到的低散,大家知道用了之后安全了就對(duì)了俯邓。
那你能說說他并發(fā)度高的原因么?
原理上來說熔号,ConcurrentHashMap 采用了分段鎖技術(shù)稽鞭,其中 Segment 繼承于 ReentrantLock。
不會(huì)像 HashTable 那樣不管是 put 還是 get 操作都需要做同步處理引镊,理論上 ConcurrentHashMap 支持 CurrencyLevel (Segment 數(shù)組數(shù)量)的線程并發(fā)朦蕴。
每當(dāng)一個(gè)線程占用鎖訪問一個(gè) Segment 時(shí)篮条,不會(huì)影響到其他的 Segment。
就是說如果容量大小是16他的并發(fā)度就是16梦重,可以同時(shí)允許16個(gè)線程操作16個(gè)Segment而且還是線程安全的兑燥。
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();//這就是為啥他不可以put null值的原因
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject
(segments, (j << SSHIFT) + SBASE)) == null)
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
他先定位到Segment,然后再進(jìn)行put操作琴拧。
我們看看他的put源代碼降瞳,你就知道他是怎么做到線程安全的了,關(guān)鍵句子我注釋了蚓胸。
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
// 將當(dāng)前 Segment 中的 table 通過 key 的 hashcode 定位到 HashEntry
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
// 遍歷該 HashEntry挣饥,如果不為空則判斷傳入的 key 和當(dāng)前遍歷的 key 是否相等,相等則覆蓋舊的 value沛膳。
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
// 不為空則需要新建一個(gè) HashEntry 并加入到 Segment 中扔枫,同時(shí)會(huì)先判斷是否需要擴(kuò)容。
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
//釋放鎖
unlock();
}
return oldValue;
}
首先第一步的時(shí)候會(huì)嘗試獲取鎖锹安,如果獲取失敗肯定就有其他線程存在競(jìng)爭(zhēng)短荐,則利用 scanAndLockForPut()
自旋獲取鎖。
- 嘗試自旋獲取鎖叹哭。
- 如果重試的次數(shù)達(dá)到了
MAX_SCAN_RETRIES
則改為阻塞鎖獲取忍宋,保證能獲取成功。
那他get的邏輯呢风罩?
get 邏輯比較簡(jiǎn)單糠排,只需要將 Key 通過 Hash 之后定位到具體的 Segment ,再通過一次 Hash 定位到具體的元素上超升。
由于 HashEntry 中的 value 屬性是用 volatile 關(guān)鍵詞修飾的入宦,保證了內(nèi)存可見性,所以每次獲取時(shí)都是最新值室琢。
ConcurrentHashMap 的 get 方法是非常高效的乾闰,因?yàn)檎麄€(gè)過程都不需要加鎖。
你有沒有發(fā)現(xiàn)1.7雖然可以支持每個(gè)Segment并發(fā)訪問研乒,但是還是存在一些問題汹忠?
是的,因?yàn)榛旧线€是數(shù)組加鏈表的方式雹熬,我們?nèi)ゲ樵兊臅r(shí)候宽菜,還得遍歷鏈表,會(huì)導(dǎo)致效率很低竿报,這個(gè)跟jdk1.7的HashMap是存在的一樣問題铅乡,所以他在jdk1.8完全優(yōu)化了。
那你再跟我聊聊jdk1.8他的數(shù)據(jù)結(jié)構(gòu)是怎么樣子的呢烈菌?
其中拋棄了原有的 Segment 分段鎖阵幸,而采用了 CAS + synchronized
來保證并發(fā)安全性花履。
跟HashMap很像,也把之前的HashEntry改成了Node挚赊,但是作用不變诡壁,把值和next采用了volatile去修飾,保證了可見性荠割,并且也引入了紅黑樹妹卿,在鏈表大于一定值的時(shí)候會(huì)轉(zhuǎn)換(默認(rèn)是8)。
同樣的蔑鹦,你能跟我聊一下他值的存取操作么夺克?以及是怎么保證線程安全的?
ConcurrentHashMap在進(jìn)行put操作的還是比較復(fù)雜的嚎朽,大致可以分為以下步驟:
- 根據(jù) key 計(jì)算出 hashcode 铺纽。
- 判斷是否需要進(jìn)行初始化。
- 即為當(dāng)前 key 定位出的 Node哟忍,如果為空表示當(dāng)前位置可以寫入數(shù)據(jù)狡门,利用 CAS 嘗試寫入,失敗則自旋保證成功锅很。
- 如果當(dāng)前位置的
hashcode == MOVED == -1
,則需要進(jìn)行擴(kuò)容融撞。 - 如果都不滿足,則利用 synchronized 鎖寫入數(shù)據(jù)粗蔚。
- 如果數(shù)量大于
TREEIFY_THRESHOLD
則要轉(zhuǎn)換為紅黑樹。
你在上面提到CAS是什么饶火?自旋又是什么鹏控?
CAS 是樂觀鎖的一種實(shí)現(xiàn)方式,是一種輕量級(jí)鎖肤寝,JUC 中很多工具類的實(shí)現(xiàn)就是基于 CAS 的当辐。
CAS 操作的流程如下圖所示,線程在讀取數(shù)據(jù)時(shí)不進(jìn)行加鎖鲤看,在準(zhǔn)備寫回?cái)?shù)據(jù)時(shí)缘揪,比較原值是否修改,若未被其他線程修改則寫回义桂,若已被修改找筝,則重新執(zhí)行讀取流程。
這是一種樂觀策略慷吊,認(rèn)為并發(fā)操作并不總會(huì)發(fā)生袖裕。
還是不明白?那我再說明下溉瓶,樂觀鎖在實(shí)際開發(fā)場(chǎng)景中非常常見急鳄,大家還是要去理解谤民。
就比如我現(xiàn)在要修改數(shù)據(jù)庫(kù)的一條數(shù)據(jù),修改之前我先拿到他原來的值疾宏,然后在SQL里面還會(huì)加個(gè)判斷张足,原來的值和我手上拿到的他的原來的值是否一樣,一樣我們就可以去修改了坎藐,不一樣就證明被別的線程修改了你就return錯(cuò)誤就好了为牍。
SQL偽代碼大概如下:
update a set value = newValue where value = #{oldValue}//oldValue就是我們執(zhí)行前查詢出來的值
CAS就一定能保證數(shù)據(jù)沒被別的線程修改過么?
并不是的顺饮,比如很經(jīng)典的ABA問題吵聪,CAS就無法判斷了。
什么是ABA兼雄?
就是說來了一個(gè)線程把值改回了B吟逝,又來了一個(gè)線程把值又改回了A,對(duì)于這個(gè)時(shí)候判斷的線程赦肋,就發(fā)現(xiàn)他的值還是A块攒,所以他就不知道這個(gè)值到底有沒有被人改過,其實(shí)很多場(chǎng)景如果只追求最后結(jié)果正確佃乘,這是沒關(guān)系的囱井。
但是實(shí)際過程中還是需要記錄修改過程的,比如資金修改什么的趣避,你每次修改的都應(yīng)該有記錄庞呕,方便回溯。
那怎么解決ABA問題程帕?
用版本號(hào)去保證就好了住练,就比如說,我在修改前去查詢他原來的值的時(shí)候再帶一個(gè)版本號(hào)愁拭,每次判斷就連值和版本號(hào)一起判斷讲逛,判斷成功就給版本號(hào)加1。
update a set value = newValue 岭埠,vision = vision + 1 where value = #{oldValue} and vision = #{vision} // 判斷原來的值和版本號(hào)是否匹配盏混,中間有別的線程修改,值可能相等惜论,但是版本號(hào)100%不一樣
牛*许赃,有點(diǎn)東西,除了版本號(hào)還有別的方法保證么来涨?
其實(shí)有很多方式图焰,比如時(shí)間戳也可以,查詢的時(shí)候把時(shí)間戳一起查出來蹦掐,對(duì)的上才修改并且更新值的時(shí)候一起修改更新時(shí)間技羔,這樣也能保證僵闯,方法很多但是跟版本號(hào)都是異曲同工之妙,看場(chǎng)景大家想怎么設(shè)計(jì)吧藤滥。
CAS性能很高鳖粟,但是我知道synchronized性能可不咋地,為啥jdk1.8升級(jí)之后反而多了synchronized拙绊?
synchronized之前一直都是重量級(jí)的鎖向图,但是后來java官方是對(duì)他進(jìn)行過升級(jí)的,他現(xiàn)在采用的是鎖升級(jí)的方式去做的标沪。
針對(duì) synchronized 獲取鎖的方式榄攀,JVM 使用了鎖升級(jí)的優(yōu)化方式,就是先使用偏向鎖優(yōu)先同一線程然后再次獲取鎖金句,如果失敗檩赢,就升級(jí)為 CAS 輕量級(jí)鎖,如果失敗就會(huì)短暫自旋违寞,防止線程被系統(tǒng)掛起贞瞒。最后如果以上都失敗就升級(jí)為重量級(jí)鎖。
所以是一步步升級(jí)上去的趁曼,最初也是通過很多輕量級(jí)的方式鎖定的军浆。
??,那我們回歸正題挡闰,ConcurrentHashMap的get操作又是怎么樣子的呢乒融?
- 根據(jù)計(jì)算出來的 hashcode 尋址,如果就在桶上那么直接返回值摄悯。
- 如果是紅黑樹那就按照樹的方式獲取值簇抵。
- 就不滿足那就按照鏈表的方式遍歷獲取值。
小結(jié):1.8 在 1.7 的數(shù)據(jù)結(jié)構(gòu)上做了大的改動(dòng)射众,采用紅黑樹之后可以保證查詢效率(O(logn)
),甚至取消了 ReentrantLock 改為了 synchronized晃财,這樣可以看出在新版的 JDK 中對(duì) synchronized 優(yōu)化是很到位的叨橱。
總結(jié)
Hashtable&ConcurrentHashMap跟HashMap基本上就是一套連環(huán)組合,我在面試的時(shí)候經(jīng)常能吹上很久断盛,經(jīng)常被面試官說:好了好了罗洗,我們繼續(xù)下一個(gè)話題吧哈哈。
是的因?yàn)樘岬紿ashMap你肯定會(huì)聊到他的線程安全性這一點(diǎn)钢猛,那你總不能加鎖一句話就搞定了吧伙菜,java的作者們也不想,所以人家寫開發(fā)了對(duì)應(yīng)的替代品命迈,那就是線程安全的Hashtable&ConcurrentHashMap贩绕。
兩者都有特點(diǎn)火的,但是線程安全場(chǎng)景還是后者用得多一點(diǎn),原因我在文中已經(jīng)大篇幅全方位的介紹了淑倾,這里就不再過多贅述了馏鹤。
你們發(fā)現(xiàn)了面試就是一個(gè)個(gè)的坑,你說到啥面試官可能就懟到你啥娇哆,別問我為啥知道嘿嘿湃累。
你知道不確定能不能為這場(chǎng)面試加分,但是不知道肯定是減分的碍讨,文中的快速失斨瘟Α(fail—fast)問到,那對(duì)應(yīng)的安全失敳颉(fail—safe)也是有可能知道的宵统,我想讀者很多都不知道吧,因?yàn)槲覇栠^很多仔哈哈溉躲。
還有提到CAS樂觀鎖榜田,你要知道ABA,你要知道解決方案锻梳,因?yàn)樵趯?shí)際的開發(fā)場(chǎng)景真的不要太常用了箭券,sync的鎖升級(jí)你也要知道。
我沒過多描述線程安全的太多東西疑枯,因?yàn)槲叶紝懥吮缈椋院蟾叮繉?duì)吧哈哈荆永。
常見問題
- 談?wù)勀憷斫獾?Hashtable废亭,講講其中的 get put 過程。ConcurrentHashMap同問具钥。
- 1.8 做了什么優(yōu)化豆村?
- 線程安全怎么做的?
- 不安全會(huì)導(dǎo)致哪些問題骂删?
- 如何解決掌动?有沒有線程安全的并發(fā)容器?
- ConcurrentHashMap 是如何實(shí)現(xiàn)的宁玫?
- ConcurrentHashMap并發(fā)度為啥好這么多粗恢?
- 1.7、1.8 實(shí)現(xiàn)有何不同欧瘪?為什么這么做眷射?
- CAS是啥?
- ABA是啥?場(chǎng)景有哪些妖碉,怎么解決涌庭?
- synchronized底層原理是啥?
- synchronized鎖升級(jí)策略
- 快速失斝岢瘛(fail—fast)是啥脾猛,應(yīng)用場(chǎng)景有哪些?安全失斢沭(fail—safe)同問猛拴。
- ......
加分項(xiàng)
在回答Hashtable和ConcurrentHashMap相關(guān)的面試題的時(shí)候,一定要知道他們是怎么保證線程安全的蚀狰,那線程不安全一般都是發(fā)生在存取的過程中的愉昆,那get、put你肯定要知道麻蹋。
HashMap是必問的那種跛溉,這兩個(gè)經(jīng)常會(huì)作為替補(bǔ)問題,不過也經(jīng)常問扮授,他們本身的機(jī)制其實(shí)都比較簡(jiǎn)單芳室,特別是ConcurrentHashMap跟HashMap是很像的,只是是否線程安全這點(diǎn)不同刹勃。
提到線程安全那你就要知道相關(guān)的知識(shí)點(diǎn)了堪侯,比如說到CAS你一定要知道ABA的問題,提到synchronized那你要知道他的原理荔仁,他鎖對(duì)象伍宦,方法、代碼塊乏梁,在底層是怎么實(shí)現(xiàn)的次洼。
synchronized你還需要知道他的鎖升級(jí)機(jī)制,以及他的兄弟ReentantLock遇骑,兩者一個(gè)是jvm層面的一個(gè)是jdk層面的卖毁,還是有很大的區(qū)別的。
那提到他們兩個(gè)你是不是又需要知道juc這個(gè)包下面的所有的常用類落萎,以及他們的底層原理了势篡?
那提到......
點(diǎn)關(guān)注,不迷路
好了各位模暗,以上就是這篇文章的全部?jī)?nèi)容了,能看到這里的人呀念祭,都是人才兑宇。
我后面會(huì)每周都更新幾篇一線互聯(lián)網(wǎng)大廠面試和常用技術(shù)棧相關(guān)的文章,非常感謝人才們能看到這里粱坤,如果這個(gè)文章寫得還不錯(cuò)隶糕,覺得「敖丙」我有點(diǎn)東西的話 求點(diǎn)贊?? 求關(guān)注?? 求分享?? 對(duì)暖男我來說真的 非常有用4刹!枚驻!
白嫖不好濒旦,創(chuàng)作不易,各位的支持和認(rèn)可再登,就是我創(chuàng)作的最大動(dòng)力尔邓,我們下篇文章見!
敖丙 | 文 【原創(chuàng)】
如果本篇博客有任何錯(cuò)誤锉矢,請(qǐng)批評(píng)指教梯嗽,不勝感激 !
文章每周持續(xù)更新沽损,可以微信搜索「 三太子敖丙 」第一時(shí)間閱讀和催更(比博客早一到兩篇喲)灯节,本文 GitHub https://github.com/JavaFamily 已經(jīng)收錄,有一線大廠面試點(diǎn)思維導(dǎo)圖绵估,也整理了很多我的文檔炎疆,歡迎Star和完善,大家面試可以參照考點(diǎn)復(fù)習(xí)国裳,希望我們一起有點(diǎn)東西形入。