《吊打面試官》系列-ConcurrentHashMap & HashTable

你知道的越多,你不知道的越多

點(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。

image

喂小鬼袭蝗,你養(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)線程安全的你有了解過么威鹿?

image

不按照套路出牌呀*轨香,正常不都是問HashMap和ConcurrentHashMap么忽你,這次怎么問了這個(gè)鬼東西,還好我飽讀詩書臂容,經(jīng)晨砌ǎ看敖丙的《吊打面試官》系列,不然真的完了脓杉。

小姐姐您這個(gè)問題真好糟秘,別的面試官都沒問過,說真的您水平肯定是頂級(jí)技術(shù)專家吧球散。

別貧嘴尿赚,快回答我的問題!抿嘴一笑??

在SynchronizedMap內(nèi)部維護(hù)了一個(gè)普通對(duì)象Map蕉堰,還有排斥鎖mutex凌净,如圖

image
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ì)方法上鎖诀拭,如圖全是??

image

臥*迁筛,小伙子,秒啊耕挨,其實(shí)我早就忘了源碼了细卧,就是瞎問一下尉桩,沒想到還是回答上來了,接下來就面對(duì)疾風(fēng)吧贪庙。

image

回答得不錯(cuò)蜘犁,能跟我聊一下Hashtable么?

這個(gè)我就等著你問呢嘿嘿止邮!

跟HashMap相比Hashtable是線程安全的这橙,適合在多線程的情況下使用,但是效率可不太樂觀导披。

哦屈扎,你能說說他效率低的原因么?

嗯嗯面試官撩匕,我看過他的源碼鹰晨,他在對(duì)數(shù)據(jù)操作的時(shí)候都會(huì)上鎖,所以效率比較低下滑沧。

image

除了這個(gè)你還能說出一些Hashtable 跟HashMap不一樣點(diǎn)么并村?

!吶呢滓技?這叫什么問題嘛哩牍?這個(gè)又是知識(shí)盲區(qū)呀!

image

呃令漂,面試官我從來沒使用過他膝昆,你容我想想?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ì)!

image

快速失斆纯埂(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í)候你都怎么處理的?

image

他來了他來了栈源,他終于還是來了挡爵,等了這么久,就是等你問我這個(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)吧:

image

如圖所示肤无,是由 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() 自旋獲取鎖。

  1. 嘗試自旋獲取鎖叹哭。
  2. 如果重試的次數(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ù)雜的嚎朽,大致可以分為以下步驟:

  1. 根據(jù) key 計(jì)算出 hashcode 铺纽。
  2. 判斷是否需要進(jìn)行初始化。
  3. 即為當(dāng)前 key 定位出的 Node哟忍,如果為空表示當(dāng)前位置可以寫入數(shù)據(jù)狡门,利用 CAS 嘗試寫入,失敗則自旋保證成功锅很。
  4. 如果當(dāng)前位置的 hashcode == MOVED == -1,則需要進(jìn)行擴(kuò)容融撞。
  5. 如果都不滿足,則利用 synchronized 鎖寫入數(shù)據(jù)粗蔚。
  6. 如果數(shù)量大于 TREEIFY_THRESHOLD 則要轉(zhuǎn)換為紅黑樹。
image

你在上面提到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ā)生袖裕。

image

還是不明白?那我再說明下溉瓶,樂觀鎖在實(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)還有別的方法保證么来涨?

image

其實(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 尋址,如果就在桶上那么直接返回值摄悯。
  • 如果是紅黑樹那就按照樹的方式獲取值簇抵。
  • 就不滿足那就按照鏈表的方式遍歷獲取值。
image

小結(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)東西形入。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市躏救,隨后出現(xiàn)的幾起案子唯笙,更是在濱河造成了極大的恐慌,老刑警劉巖盒使,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件崩掘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡少办,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門挽放,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辑畦,你說我怎么就攤上這事〈砍觯” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵暂筝,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我焕襟,道長(zhǎng),這世上最難降的妖魔是什么鸵赖? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任务漩,我火速辦了婚禮,結(jié)果婚禮上卫漫,老公的妹妹穿的比我還像新娘菲饼。我一直安慰自己,他們只是感情好列赎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布宏悦。 她就那樣靜靜地躺著,像睡著了一般包吝。 火紅的嫁衣襯著肌膚如雪饼煞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天诗越,我揣著相機(jī)與錄音砖瞧,去河邊找鬼。 笑死嚷狞,一個(gè)胖子當(dāng)著我的面吹牛块促,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播床未,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼竭翠,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了薇搁?” 一聲冷哼從身側(cè)響起斋扰,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎啃洋,沒想到半個(gè)月后传货,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宏娄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年问裕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孵坚。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡粮宛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情逗堵,我是刑警寧澤蜒秤,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站攘滩,受9級(jí)特大地震影響漂问,放射性物質(zhì)發(fā)生泄漏女揭。R本人自食惡果不足惜吧兔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一境蔼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逢享,春花似錦拼苍、人聲如沸调缨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽燕侠。三九已至,卻和暖如春七问,著一層夾襖步出監(jiān)牢的瞬間械巡,已是汗流浹背饶氏。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工疹启, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留喊崖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓趋急,卻偏偏與公主長(zhǎng)得像呜达,于是被迫代替她去往敵國(guó)和親查近。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挤忙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 本系列出于AWeiLoveAndroid的分享册烈,在此感謝赏僧,再結(jié)合自身經(jīng)驗(yàn)查漏補(bǔ)缺,完善答案挽绩。以成系統(tǒng)唉堪。 Java基...
    濟(jì)公大將閱讀 1,528評(píng)論 1 6
  • Java SE 基礎(chǔ): 封裝、繼承唠亚、多態(tài) 封裝: 概念:就是把對(duì)象的屬性和操作(或服務(wù))結(jié)合為一個(gè)獨(dú)立的整體灶搜,并盡...
    Jayden_Cao閱讀 2,108評(píng)論 0 8
  • 常說“所以說”的人,通常愛獨(dú)攬功勞瞬浓。 日常生活中猿棉,很多人說話的時(shí)候都會(huì)有一些口頭禪屑咳,好像沒有它們表述的就不夠完整舒...
    涼山博雨閱讀 605評(píng)論 0 1
  • 今天笑來老師的專欄文章的名字叫做:出售時(shí)間之前你要牢記的三條鐵律兆龙。 文章首先帶我們重溫了什么是財(cái)富自由,即再也不用...
    星空下的可麗餅閱讀 127評(píng)論 0 0
  • 桃花三月春日暖 斜風(fēng)夕照幾行詩 遠(yuǎn)燈歸來依山色 閑云微卷晚暮遲 本文已在版權(quán)印備案,如需轉(zhuǎn)載請(qǐng)?jiān)L問版權(quán)印01149621
    洛子風(fēng)閱讀 927評(píng)論 0 0