最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Java并發(fā)編程面試題(3)

2023-02-21 18:36 作者:茶曉i  | 我要投稿

一、CountDownLatch,Semaphore的高頻問題:

1.1 CountDownLatch是啥?有啥用?底層咋實(shí)現(xiàn)的?(可以融入到你的項(xiàng)目業(yè)務(wù)中。)

CountDownLatch本質(zhì)其實(shí)就是一個(gè)計(jì)數(shù)器。

在多線程并形處理業(yè)務(wù)時(shí),需要等待其他線程處理完,再做后續(xù)的合并等操作,再響應(yīng)用戶時(shí),可以使用CountDownLatch做計(jì)數(shù),等到其他線程出現(xiàn)完之后,主線程就會(huì)被喚醒。

CountDownLatch本身就是基于AQS實(shí)現(xiàn)的。

new CountDownLatch時(shí),直接指定好具體的數(shù)值。這個(gè)數(shù)值會(huì)復(fù)制給state屬性。

當(dāng)子線程處理完任務(wù)后,執(zhí)行countDown方法,內(nèi)部就是直接給state - 1而已。

當(dāng)state減為0之后,執(zhí)行await掛起的線程,就會(huì)被喚醒。

CountDownLatch不能重復(fù)使用,用完就涼涼。

1.2 Semaphore是啥?有啥用?底層咋實(shí)現(xiàn)的?

信號(hào)量,就是一個(gè)可以用于做限流功能的工具類。

比如Hystrix中涉及到了信號(hào)量隔離,要求并發(fā)的線程數(shù)有限制,就可以使用信號(hào)量實(shí)現(xiàn)。

比如要求當(dāng)前服務(wù)最多10個(gè)線程同時(shí)干活,將信號(hào)量設(shè)置為10。沒一個(gè)任務(wù)提交都需要獲取一個(gè)信號(hào)量,就去干活,干完了,歸還信號(hào)量。

信號(hào)量也是基于AQS實(shí)現(xiàn)的。

構(gòu)建信號(hào)量時(shí),指定信號(hào)量資源數(shù),獲取時(shí),指定獲取幾個(gè)信號(hào)量,也是CAS保證原子性,歸還也是類似的。

1.3 main線程結(jié)束,程序會(huì)停止嘛?

如果main線程結(jié)束,但是還有用戶線程在執(zhí)行,不會(huì)結(jié)束!

如果main線程結(jié)束,剩下的都是守護(hù)線程,結(jié)束!

二、CopyOnWriteArrayList的高頻問題:

2.1 CopyOnWriteArrayList是如何保證線程安全的?有什么缺點(diǎn)嗎?

CopyOnWriteArrayList寫數(shù)據(jù)時(shí),是基于ReentrantLock保證原子性的。

其次,寫數(shù)據(jù)時(shí),會(huì)復(fù)制一個(gè)副本寫入,寫入成功后,才會(huì)寫入到CopyOnWriteArrayList中的數(shù)組。

保證讀數(shù)據(jù)時(shí),不要出現(xiàn)數(shù)據(jù)不一致的問題。

如果數(shù)據(jù)量比較大時(shí),每次寫入數(shù)據(jù),都需要復(fù)制一個(gè)副本,對(duì)空間的占用太大了。如果數(shù)據(jù)量比較大,不推薦使用CopyOnWriteArrayList。

寫操作要求保證原子性,讀操作保證并發(fā),并且數(shù)據(jù)量不大 ~

三、ConcurrentHashMap(JDK1.8)的高頻問題:

3.1 HashMap為啥線程不安全?

問題1:JDK1.7里有環(huán)(擴(kuò)容時(shí))。

問題2:數(shù)據(jù)會(huì)覆蓋,數(shù)據(jù)可能丟失。

問題3:其次計(jì)數(shù)器,也是傳統(tǒng)的++,在記錄元素個(gè)數(shù)和HashMap寫的次數(shù)時(shí),記錄不準(zhǔn)確。

問題4:數(shù)據(jù)遷移,擴(kuò)容,也可能會(huì)丟失數(shù)據(jù)。

3.2 ConcurrentHashMap如何保證線程安全的?

回答1:尾插,其次擴(kuò)容有CAS保證線程安全

回答2:寫入數(shù)組時(shí),基于CAS保證安全,掛入鏈表或插入紅黑樹時(shí),基于synchronized保證安全。

回答3:這里ConcurrentHashMap是采用LongAdder實(shí)現(xiàn)的技術(shù),底層還是CAS。(AtomicLong)

回答4:ConcurrentHashMap擴(kuò)容時(shí),一點(diǎn)基于CAS保證數(shù)據(jù)遷移不出現(xiàn)并發(fā)問題, 其次ConcurrentHashMap還提供了并發(fā)擴(kuò)容的操作。舉個(gè)例子,數(shù)組長度64擴(kuò)容為128,兩個(gè)線程同時(shí)擴(kuò)容的話,

線程A領(lǐng)取64-48索引的數(shù)據(jù)遷移任務(wù),線程B領(lǐng)取47-32的索引數(shù)據(jù)遷移任務(wù)。關(guān)鍵是領(lǐng)取任務(wù)時(shí),是基于CAS保證線程安全的。

3.3 ConcurrentHashMap構(gòu)建好,數(shù)組就創(chuàng)建出來了嗎?如果不是,如何保證初始化數(shù)組的線程安全?

ConcurrentHashMap是懶加載的機(jī)制,而且大多數(shù)的框架組件都是懶加載的~

基于CAS來保證初始化線程安全的,這里不但涉及到了CAS去修改sizeCtl的變量去控制線程初始化數(shù)據(jù)的原子性,同時(shí)還使用了DCL,外層判斷數(shù)組未初始化,中間基于CAS修改sizeCtl,內(nèi)層再做數(shù)組未初始化判斷。

image.png

3.4 為什么負(fù)載因子是0.75,為什么鏈表長度到8轉(zhuǎn)為紅黑樹?

而且ConcurrentHashMap的負(fù)載因子不允許修改!

負(fù)載因子是0.75從兩個(gè)方面去解釋。

為啥不是0.5,為啥不是1?

0.5:如果負(fù)載因子是0.5,數(shù)據(jù)添加一半就開始擴(kuò)容了

  • 優(yōu)點(diǎn):hash碰撞少,查詢效率高。

  • 缺點(diǎn):擴(kuò)容太頻繁,而且空間利用率低。

1:如果負(fù)載因子是1,數(shù)據(jù)添加到數(shù)組長度才開始擴(kuò)容

  • 優(yōu)點(diǎn):擴(kuò)容不頻繁,空間利用率可以的。

  • 缺點(diǎn):hash沖突會(huì)特別頻繁,數(shù)據(jù)掛到鏈表上,影響查詢效率,甚至鏈表過長生成紅黑樹,導(dǎo)致寫入的效率也收到影響。

0.75就可以說是一個(gè)居中的選擇,兩個(gè)方面都兼顧了。

再聊就是泊松分布,在負(fù)載因子是0.75時(shí),根據(jù)泊松分布得出,鏈表長度達(dá)到8的概率是非常低的,源碼中的標(biāo)識(shí)是0.00000006,生成紅黑樹的概率特別低。

雖然ConcurrentHashMap引入了紅黑樹,但是紅黑樹對(duì)于寫入的維護(hù)成本更高,能不用就不用,HashMap源碼的注釋也描述了,要盡可能規(guī)避紅黑樹。

至于6退化為鏈表,是因?yàn)闈M樹是7個(gè)值,7不退化是為了防止頻繁的鏈表和紅黑樹轉(zhuǎn)換,這里6退化留下了一個(gè)中間值,避免頻繁的轉(zhuǎn)換。

put操作太頻繁的場(chǎng)景,會(huì)造成擴(kuò)容時(shí)期put的阻塞 ?

一般情況下不會(huì)造成阻塞。

因?yàn)槿绻趐ut操作時(shí),發(fā)現(xiàn)當(dāng)前索引位置并沒有數(shù)據(jù)時(shí),正常把數(shù)據(jù)落到老數(shù)組上。

如果put操作時(shí),發(fā)現(xiàn)當(dāng)前位置數(shù)據(jù)已經(jīng)被遷移到了新數(shù)組,這時(shí)無法正常插入,去幫助擴(kuò)容,快速結(jié)束擴(kuò)容操作,并且重新選擇索引位置查詢

3.5 ConcurrentHashMap何時(shí)擴(kuò)容,擴(kuò)容的流程是什么?

  • ConcurrentHashMap中的元素個(gè)數(shù),達(dá)到了負(fù)載因子計(jì)算的閾值,那么直接擴(kuò)容

  • 當(dāng)調(diào)用putAll方法,查詢大量數(shù)據(jù)時(shí),也可能會(huì)造成直接擴(kuò)容的操作,大量數(shù)據(jù)是如果插入的數(shù)據(jù)大于下次擴(kuò)容的閾值,直接擴(kuò)容,然后再插入

  • 數(shù)組長度小于64,并且鏈表長度大于等于8時(shí),會(huì)觸發(fā)擴(kuò)容

image.png

擴(kuò)容的流程:(sizeCtl是一個(gè)int類型變量,用于控制初始化和擴(kuò)容)

  • 每個(gè)擴(kuò)容的線程都需要基于oldTable的長度計(jì)算一個(gè)擴(kuò)容標(biāo)識(shí)戳(避免出現(xiàn)兩個(gè)擴(kuò)容線程的數(shù)組長度不一致。其次保證擴(kuò)容標(biāo)識(shí)戳的16位是1,這樣左移16位會(huì)得到一個(gè)負(fù)數(shù))

  • 第一個(gè)擴(kuò)容的線程,會(huì)對(duì)sizeCtl + 2,代表當(dāng)前有1個(gè)線程來擴(kuò)容

  • 除去第一個(gè)擴(kuò)容的線程,其他線程會(huì)對(duì)sizeCtl + 1,代表現(xiàn)在又來了一個(gè)線程幫助擴(kuò)容

  • 第一個(gè)線程會(huì)初始化新數(shù)組。

  • 每個(gè)線程會(huì)領(lǐng)取遷移數(shù)據(jù)的任務(wù),將oldTable中的數(shù)據(jù)遷移到newTable。默認(rèn)情況下,每個(gè)線程每次領(lǐng)取長度為16的遷移數(shù)據(jù)任務(wù)

  • 當(dāng)數(shù)據(jù)遷移完畢時(shí),每個(gè)線程再去領(lǐng)取任務(wù)時(shí),發(fā)現(xiàn)沒任務(wù)可領(lǐng)了,退出擴(kuò)容,對(duì)sizeCtl - 1。

  • 最后一個(gè)退出擴(kuò)容的線程,發(fā)現(xiàn)-1之后,還剩1,最后一個(gè)退出擴(kuò)容的線程會(huì)從頭到尾再檢查一次,有沒有遺留的數(shù)據(jù)沒有遷移走(這種情況基本不發(fā)生),檢查完之后,再-1,這樣sizeCtl扣除完,擴(kuò)容結(jié)束。

3.6 ConcurrentHashMap得計(jì)數(shù)器如何實(shí)現(xiàn)的?

這里是基于LongAdder的機(jī)制實(shí)現(xiàn),但是并沒有直接用LongAdder的引用,而是根據(jù)LongAdder的原理寫了一個(gè)相似度在80%以上的代碼,直接使用。

LongAdder使用CAS添加,保證原子性,其次基于分段鎖,保證并發(fā)性。

3.7 ConcurrentHashMap的讀操作會(huì)阻塞嘛?

無論查哪,都不阻塞。

查詢數(shù)組? :第一塊就是查看元素是否在數(shù)組,在就直接返回。

查詢鏈表?:第二塊,如果沒特殊情況,就在鏈表next,next查詢即可。

擴(kuò)容時(shí)?:第三塊,如果當(dāng)前索引位置是-1,代表當(dāng)前位置數(shù)據(jù)全部都遷移到了新數(shù)組,直接去新數(shù)組查詢,不管有沒有擴(kuò)容完。

查詢紅黑樹?:如果有一個(gè)線程正在寫入紅黑樹,此時(shí)讀線程還能去紅黑樹查詢嗎?因?yàn)榧t黑樹為了保證平衡可能會(huì)旋轉(zhuǎn),旋轉(zhuǎn)會(huì)換指針,可能會(huì)出現(xiàn)問題。所以在轉(zhuǎn)換紅黑樹時(shí),不但有一個(gè)紅黑樹,還會(huì)保留一個(gè)雙向鏈表,此時(shí)會(huì)查詢雙向鏈表,不讓讀線程阻塞。至于如何判斷是否有線程在寫,和等待寫或者是讀紅黑樹,根據(jù)TreeBin的lockState來判斷,如果是1,代表有線程正在寫,如果為2,代表有寫線程等待寫,如果是4n,代表有多個(gè)線程在做讀操作。


Java并發(fā)編程面試題(3)的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國家法律
天长市| 安吉县| 三江| 扶绥县| 河池市| 疏勒县| 武汉市| 于田县| 皮山县| 德钦县| 柞水县| 绥宁县| 沙田区| 同德县| 三亚市| 高州市| 色达县| 平遥县| 多伦县| 含山县| 耿马| 阿拉善盟| 乌恰县| 平阳县| 吉安市| 星座| 焉耆| 宜城市| 长春市| 成武县| 巨野县| 邵武市| 武平县| 高安市| 西盟| 英吉沙县| 合水县| 连城县| 定襄县| 徐汇区| 湾仔区|