JUC 相關(guān)面試題
談?wù)勈裁词蔷€程池
線程池和數(shù)據(jù)庫(kù)連接池非常類(lèi)似,可以統(tǒng)一管理和維護(hù)線程,減少?zèng)]有必要的開(kāi)銷(xiāo)。
為什么要使用線程池
因?yàn)轭l繁的開(kāi)啟線程或者停止線程,線程需要從新被 cpu 從就緒到運(yùn)行狀態(tài)調(diào)度,需要發(fā)生上下文切換
你們哪些地方會(huì)使用到線程池
實(shí)際開(kāi)發(fā)項(xiàng)目中 禁止自己 new 線程。必須使用線程池來(lái)維護(hù)和創(chuàng)建線程。
線程池有哪些作用
核心點(diǎn):復(fù)用機(jī)制 提前創(chuàng)建好固定的線程一直在運(yùn)行狀態(tài) 實(shí)現(xiàn)復(fù)用 限制線程創(chuàng)建數(shù)量。
-
降低資源消耗:通過(guò)池化技術(shù)重復(fù)利用已創(chuàng)建的線程,降低線程創(chuàng)建和銷(xiāo)毀造成的損耗。
-
提高響應(yīng)速度:任務(wù)到達(dá)時(shí),無(wú)需等待線程創(chuàng)建即可立即執(zhí)行。
-
提高線程的可管理性:線程是稀缺資源,如果無(wú)限制創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)因?yàn)榫€程的不合理分布導(dǎo)致資源調(diào)度失衡,降低系統(tǒng)的穩(wěn)定性。使用線程池可以進(jìn)行統(tǒng)一的分配、調(diào)優(yōu)和監(jiān)控。
-
提供更多更強(qiáng)大的功能:線程池具備可拓展性,允許開(kāi)發(fā)人員向其中增加更多的功能。比如延時(shí)定時(shí)線程池 ScheduledThreadPoolExecutor,就允許任務(wù)延期執(zhí)行或定期執(zhí)行。
線程池的創(chuàng)建方式
// 可緩存線程池
Executors.newCachedThreadPool();
// 可定長(zhǎng)度 限制最大線程數(shù)
Executors.newFixedThreadPool();
// 可定時(shí)
Executors.newScheduledThreadPool();
// 單例
Executors.newSingleThreadExecutor();
底層都是基于 ThreadPoolExecutor 構(gòu)造函數(shù)封裝
線程池底層是如何實(shí)現(xiàn)復(fù)用的
本質(zhì)思想:創(chuàng)建一個(gè)線程,不會(huì)立馬停止或者銷(xiāo)毀而是一直實(shí)現(xiàn)復(fù)用。
-
提前創(chuàng)建固定大小的線程一直保持在正在運(yùn)行狀態(tài);(可能會(huì)非常消耗 cpu 的資源)
-
當(dāng)需要線程執(zhí)行任務(wù),將該任務(wù)提交緩存在并發(fā)隊(duì)列中;如果緩存隊(duì)列滿了,則會(huì)執(zhí)行拒絕策略;
-
正在運(yùn)行的線程從并發(fā)隊(duì)列中獲取任務(wù)執(zhí)行從而實(shí)現(xiàn)多線程復(fù)用問(wèn)題;
線程池核心點(diǎn):復(fù)用機(jī)制 ------
- 提前創(chuàng)建好固定的線程一直在運(yùn)行狀態(tài) 死循環(huán)實(shí)現(xiàn)
- 提交的線程任務(wù)緩存到一個(gè)并發(fā)隊(duì)列集合中,交給我們正在運(yùn)行的線程執(zhí)行
- 正在運(yùn)行的線程就從隊(duì)列中獲取該任務(wù)執(zhí)行簡(jiǎn)單模擬手寫(xiě) Java 線程池:
corePoolSize:核心線程數(shù)量 一直正在保持運(yùn)行的線程
maximumPoolSize:最大線程數(shù),線程池允許創(chuàng)建的最大線程數(shù)。
keepAliveTime:超出
corePoolSize 后創(chuàng)建的線程的存活時(shí)間。
unit:keepAliveTime 的時(shí)間單位。
workQueue:任務(wù)隊(duì)列,用于保存待執(zhí)行的任務(wù)。
threadFactory:線程池內(nèi)部創(chuàng)建線程所用的工廠。
handler:任務(wù)無(wú)法執(zhí)行時(shí)的處理器。
線程池創(chuàng)建的線程會(huì)一直在運(yùn)行狀態(tài)嗎?
不會(huì)
例如:配置核心線程數(shù) corePoolSize 為 2 、最大線程數(shù) maximumPoolSize 為 5
我們可以通過(guò)配置超出 corePoolSize 核心線程數(shù)后創(chuàng)建的線程的存活時(shí)間例如為 60s
在 60s 內(nèi)沒(méi)有核心線程一直沒(méi)有任務(wù)執(zhí)行,則會(huì)停止該線程。
為什么阿里巴巴不建議使用 Executors
因?yàn)槟J(rèn)的 Executors 線程池底層是基于 ThreadPoolExecutor
構(gòu)造函數(shù)封裝的,采用無(wú)界隊(duì)列存放緩存任務(wù),會(huì)無(wú)限緩存任務(wù)容易發(fā)生內(nèi)存溢出,會(huì)導(dǎo)致我們最大線程數(shù)會(huì)失效。
線程池底層 ThreadPoolExecutor 底層實(shí)現(xiàn)原理
-
當(dāng)線程數(shù)小于核心線程數(shù)時(shí),創(chuàng)建線程。
-
當(dāng)線程數(shù)大于等于核心線程數(shù),且任務(wù)隊(duì)列未滿時(shí),將任務(wù)放入任務(wù)隊(duì)列。
-
當(dāng)線程數(shù)大于等于核心線程數(shù),且任務(wù)隊(duì)列已滿
-
若線程數(shù)小于最大線程數(shù),創(chuàng)建線程
-
若線程數(shù)等于最大線程數(shù),拋出異常,拒絕任務(wù)
線程池隊(duì)列滿了,任務(wù)會(huì)丟失嗎
如果隊(duì)列滿了,且任務(wù)總數(shù)>最大線程數(shù)則當(dāng)前線程走拒絕策略。
可以自定義異拒絕異常,將該任務(wù)緩存到 redis、本地文件、mysql 中后期項(xiàng)目啟動(dòng)實(shí)現(xiàn)補(bǔ)償。
線程池拒絕策略類(lèi)型有哪些呢
1.AbortPolicy 丟棄任務(wù),拋運(yùn)行時(shí)異常
2.CallerRunsPolicy 執(zhí)行任務(wù)
3.DiscardPolicy 忽視,什么都不會(huì)發(fā)生
4. DiscardOldestPolicy 從隊(duì)列中踢出最先進(jìn)入隊(duì)列(最后一個(gè)執(zhí)行)的任務(wù)
5. 實(shí)現(xiàn) RejectedExecutionHandler 接口,可自定義處理器
線程池如何合理配置參數(shù)
自定義線程池就需要我們自己配置最大線程數(shù) maximumPoolSize,為了高效的并發(fā)運(yùn)行,當(dāng)然這個(gè)不能隨便設(shè)置。這時(shí)需要看我們的業(yè)務(wù)是 IO 密集型還是 CPU 密集型。
CPU 密集型
CPU 密集的意思是該任務(wù)需要大量的運(yùn)算,而沒(méi)有阻塞,CPU 一直全速運(yùn)行。
CPU 密集任務(wù)只有在真正的多核 CPU 上才可能得到加速(通過(guò)多線程),而在單核 CPU 上,無(wú)論你開(kāi)幾個(gè)模擬的多線程該任務(wù)都不可能得到加速,因?yàn)?CPU 總的運(yùn)算能力就那些。
CPU 密集型任務(wù)配置盡可能少的線程數(shù)量:以保證每個(gè) CPU 高效的運(yùn)行一個(gè)線程。一般公式:(CPU 核數(shù)+1)個(gè) 線程的線程池
IO 密集型
I0 密集型,即該任務(wù)需要大量的 IO,即大量的阻塞。在單線程上運(yùn)行 I0 密集型的任務(wù)會(huì)導(dǎo)致浪費(fèi)大量的 CPU 運(yùn)算能力浪費(fèi)在等待。
所以在 IO 密集型任務(wù)中使用多線程可以大大的加速程序運(yùn)行,即使在單核 CPU 上,這種加速主要就是利用了被浪費(fèi)掉的阻塞時(shí)間。
I0 密集型時(shí),大部分線程都阻寒,故需要多配置線程數(shù):公式:
CPU 核數(shù) * 2
CPU 核數(shù) / (1 - 阻塞系數(shù)) 阻塞系數(shù) 在 0.8~0.9 之間查看 CPU 核數(shù):
System.out.println(Runtime.getRuntime().availableProcessors());
什么是悲觀鎖?什么是樂(lè)觀鎖
悲觀鎖:
-
站在 mysql 的角度分析:悲觀鎖就是比較悲觀,當(dāng)多個(gè)線程對(duì)同一行數(shù)據(jù)實(shí)現(xiàn)修改的時(shí)候,最后只有一個(gè)線程才能修改成功,只要誰(shuí)能夠?qū)Λ@取到行鎖則其他線程時(shí)不能夠?qū)υ摂?shù)據(jù)做任何修改操作,且是阻塞狀態(tài)。
-
站在 java 鎖層面,如果沒(méi)有獲取到鎖,則會(huì)阻塞等待,后期喚醒的鎖的成本就會(huì)非常高,從新被我們 cpu 從就緒調(diào)度為運(yùn)行狀態(tài)。
Lock syn 鎖 悲觀鎖沒(méi)有獲取到鎖的線程會(huì)阻塞等待;
樂(lè)觀鎖:
樂(lè)觀鎖比較樂(lè)觀,通過(guò)預(yù)值或者版本號(hào)比較,如果不一致性的情況則通過(guò)循環(huán)控制修改,當(dāng)前線程不會(huì)被阻塞,是樂(lè)觀,效率比較高,但是樂(lè)觀鎖比較消耗 cpu 的資源。
樂(lè)觀鎖:獲取鎖----如果沒(méi)有獲取到鎖,當(dāng)前線程是不會(huì)阻塞等待 通過(guò)死循環(huán)控制。樂(lè)觀鎖屬于無(wú)鎖機(jī)制,沒(méi)有競(jìng)爭(zhēng)鎖流程。
注意:mysql 的 innodb 引擎中存在行鎖的概念
Mysql 層面如何實(shí)現(xiàn)樂(lè)觀鎖呢
在我們表結(jié)構(gòu)中,會(huì)新增一個(gè)字段就是版本字段
version
varchar(255) DEFAULT NULL,
多個(gè)線程對(duì)同一行數(shù)據(jù)實(shí)現(xiàn)修改操作,提前查詢當(dāng)前最新的 version 版本號(hào)碼,
作為 update 條件查詢,如果當(dāng)前 version 版本號(hào)碼發(fā)生了變化,則查詢不到該數(shù)據(jù)。表示如果修改數(shù)據(jù)失敗,則不斷重試 ,有從新查詢最新的版本實(shí)現(xiàn) update。
需要注意控制樂(lè)觀鎖循環(huán)的次數(shù),避免 cpu 飆高的問(wèn)題。
mysql 的 innodb 引擎中存在行鎖的概念
樂(lè)觀鎖實(shí)現(xiàn)方式
Java 有哪些鎖的分類(lèi)呢
-
悲觀與樂(lè)觀鎖
-
公平鎖與非公平鎖
-
自旋鎖/重入鎖
-
重量級(jí)鎖與輕量級(jí)鎖
-
獨(dú)占鎖與共享鎖
公平鎖與非公平鎖之間的區(qū)別
公平鎖:就是比較公平,根據(jù)請(qǐng)求鎖的順序排列,先來(lái)請(qǐng)求的就先獲取鎖,后來(lái)獲取鎖就最后獲取到, 采用隊(duì)列存放 類(lèi)似于吃飯排隊(duì)。
非公平鎖:不是據(jù)請(qǐng)求的順序排列, 通過(guò)爭(zhēng)搶的方式獲取鎖。非公平鎖效率是公平鎖效率要高,Synchronized 是非公平鎖
New ReentramtLock()(true)—公平鎖 New ReentramtLock()(false)—非公平鎖底層基于 aqs 實(shí)現(xiàn)
公平鎖底層是如何實(shí)現(xiàn)的
公平鎖:就是比較公平,根據(jù)請(qǐng)求鎖的順序排列,先來(lái)請(qǐng)求的就先獲取鎖,后來(lái)獲取鎖就最后獲取到, 采用隊(duì)列存放 類(lèi)似于吃飯排隊(duì)。
隊(duì)列—底層實(shí)現(xiàn)方式—數(shù)組或者鏈表實(shí)現(xiàn)
獨(dú)占鎖與共享鎖之間的區(qū)別
獨(dú)占鎖:在多線程中,只允許有一個(gè)線程獲取到鎖,其他線程都會(huì)等待。
共享鎖:多個(gè)線程可以同時(shí)持有鎖,例如 ReentrantLock 讀寫(xiě)鎖。讀讀可以共享、寫(xiě)寫(xiě)互斥、讀寫(xiě)互斥、寫(xiě)讀互斥。
什么是鎖的可重入性
在同一個(gè)線程中鎖可以不斷傳遞的,可以直接獲取。
Syn/lock aqs
什么是 CAS(自旋鎖),它的優(yōu)缺點(diǎn)
Syn/lock CAS
沒(méi)有獲取到鎖的線程是不會(huì)阻塞的,通過(guò)循環(huán)控制一直不斷的獲取鎖。
CAS: Compare and Swap,翻譯成比較并交換。 執(zhí)行函數(shù) CAS(V,E,N)
CAS 有 3 個(gè)操作數(shù),內(nèi)存值 V,舊的預(yù)期值 E,要修改的新值 N。當(dāng)且僅當(dāng)預(yù)期值 E 和內(nèi)存值 V 相同時(shí),將內(nèi)存值 V 修改為 N,否則什么都不做。
-
Cas 是通過(guò)硬件指令,保證原子性
-
Java 是通過(guò) unsafe jni 技術(shù)
原子類(lèi): AtomicBoolean,AtomicInteger,AtomicLong 等使用 CAS 實(shí)現(xiàn)。
優(yōu)點(diǎn):沒(méi)有獲取到鎖的線程,會(huì)一直在用戶態(tài),不會(huì)阻塞,沒(méi)有鎖的線程會(huì)一直通過(guò)循環(huán)控制重試。
缺點(diǎn):通過(guò)死循環(huán)控制,消耗 cpu 資源比較高,需要控制循次數(shù),避免 cpu 飆高問(wèn)題;
Cas 本質(zhì)的原理:
舊的預(yù)期值===v(共享變量中值),才會(huì)修改我們 v。
基于 cas 實(shí)現(xiàn)鎖機(jī)制原理
Cas 無(wú)鎖機(jī)制原理:
1. 定義一個(gè)鎖的狀態(tài);
2. 狀態(tài)狀態(tài)值=0 則表示沒(méi)有線程獲取到該鎖;
3. 狀態(tài)狀態(tài)值=1 則表示有線程已經(jīng)持有該鎖;
實(shí)現(xiàn)細(xì)節(jié):
CAS 獲取鎖:
將該鎖的狀態(tài)從 0 改為 1-----能夠修改成功 cas 成功則表示獲取鎖成功
果獲取鎖失敗–修改失敗,則不會(huì)阻塞而是通過(guò)循環(huán)(自旋來(lái)控制重試)
如 CAS 釋放鎖:
將該鎖的狀態(tài)從 1 改為 0 如果能夠改成功 cas 成功則表示釋放鎖成功。
CAS 如何解決 ABA 的問(wèn)題
Cas 主要檢查 內(nèi)存值 V 與舊的預(yù)值值=E 是否一致,如果一致的情況下,則修改。這時(shí)候會(huì)存在 ABA 的問(wèn)題:
如果將原來(lái)的值 A,改為了 B,B 有改為了 A 發(fā)現(xiàn)沒(méi)有發(fā)生變化,實(shí)際上已經(jīng)發(fā)生了變化,所以存在 Aba 問(wèn)題。
解決辦法:通過(guò)版本號(hào)碼,對(duì)每個(gè)變量更新的版本號(hào)碼做+1
解決 aba 問(wèn)題是否大:概念產(chǎn)生沖突,但是不影響結(jié)果,換一種方式 通過(guò)版本號(hào)碼方式。
利用原子類(lèi)手寫(xiě) CAS 無(wú)鎖
談?wù)勀銓?duì) Threadlocal 理解?
ThreadLocal 提供了線程本地變量,它可以保證訪問(wèn)到的變量屬于當(dāng)前線程,每個(gè)線程都保存有一個(gè)變量副本,每個(gè)線程的變量都不同。ThreadLocal 相當(dāng)于提供了一種線程隔離,將變量與線程相綁定。
*Threadlocal* 適用于在多線程的情況下,可以實(shí)現(xiàn)傳遞數(shù)據(jù),實(shí)現(xiàn)線程隔離。
*ThreadLocal* 提供給我們每個(gè)線程緩存局部變量
Threadlocal 基本 API
-
New Threadlocal();—?jiǎng)?chuàng)建 Threadlocal 2.set 設(shè)置當(dāng)前線程綁定的局部變量
-
get 獲取當(dāng)前線程綁定的局部變量
-
remove() 移除當(dāng)前線程綁定的變量
哪些地方有使用 Threadlocal
1.Spring 事務(wù)模板類(lèi) 2.獲取 HttpRequest 3.Aop 調(diào)用鏈
Threadlocal 底層實(shí)現(xiàn)原理
-
在每個(gè)線程中都有自己獨(dú)立的 ThreadLocalMap 對(duì)象,中 Entry 對(duì)象。
-
如果當(dāng)前線程對(duì)應(yīng)的的 ThreadLocalMap 對(duì)象為空的情況下,則創(chuàng)建該 ThreadLocalMap
對(duì)象,并且賦值鍵值對(duì)。 Key 為 當(dāng)前 new ThreadLocal 對(duì)象,value 就是為 object 變量值。
為什么線程緩存的是 ThreadlocalMap 對(duì)象
ThreadLocalMap 可以存放 n 多個(gè)不同的 ThreadLocal 對(duì)象;每個(gè) ThreadLocal 對(duì)象只能緩存一個(gè)變量值;
ThreadLocalMap<ThreadLocal 對(duì)象,value> threadLocalMap ThreadLocal.get();
threadLocalMap.get(ThreadLocal) 緩存變量值
談?wù)剰?qiáng)、軟、弱、虛引用 區(qū)別
強(qiáng)引用: 當(dāng)內(nèi)存不足時(shí),JVM 開(kāi)始進(jìn)行 GC(垃圾回收),對(duì)于強(qiáng)引用對(duì)象,就算是出現(xiàn)了 OOM 也不會(huì)對(duì)該對(duì)象進(jìn)行回收,死都不會(huì)收。
軟引用:當(dāng)系統(tǒng)內(nèi)存充足的時(shí)候,不會(huì)被回收;當(dāng)系統(tǒng)內(nèi)存不足時(shí),它會(huì)被回收,軟引用通常用在對(duì)內(nèi)存敏感的 程序中,比如高速緩存就用到軟引用,內(nèi)存夠用時(shí)就保留,不夠時(shí)就回收。
弱引用:弱引用需要用到 java.lang.ref.WeakReference 類(lèi)來(lái)實(shí)現(xiàn),它比軟引用的生存周期更短。對(duì)于只有弱引用的對(duì)象來(lái)說(shuō),只要有垃圾回收,不管 JVM 的內(nèi)存空間夠不夠用,都會(huì)回收該對(duì)象占用的內(nèi)存空間。
虛:虛引用需要 java.lang.ref.Phantomreference 類(lèi)來(lái)實(shí)現(xiàn)。顧名思義,虛引用就是形同虛設(shè)。與其它幾種引用不同,虛引用并不會(huì)決定對(duì)象的聲明周期。
Threadlocal 為何引發(fā)內(nèi)存泄漏問(wèn)題
補(bǔ)充概念:
什么是內(nèi)存泄漏問(wèn)題
內(nèi)存泄漏 表示就是我們程序員申請(qǐng)了內(nèi)存,但是該內(nèi)存一直無(wú)法釋放;內(nèi)存泄漏溢出問(wèn)題:
申請(qǐng)內(nèi)存時(shí),發(fā)現(xiàn)申請(qǐng)內(nèi)存不足,就會(huì)報(bào)錯(cuò) 內(nèi)存溢出的問(wèn)題;
因?yàn)槊總€(gè)線程中都有自己獨(dú)立的 ThreadLocalMap 對(duì)象,key 為 ThreadLocal,value 是為變量值。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-434930.html
Key 為 ThreadLocal 作為 Entry 對(duì)象的 key,是弱引用,當(dāng) ThreadLocal 指向 null 的時(shí)候, Entry 對(duì)象中的 key 變?yōu)?null,GC 如果沒(méi)有清理垃圾時(shí),則該對(duì)象會(huì)一直無(wú)法被垃圾收集機(jī)制回收,一直占用到了系統(tǒng)內(nèi)存,有可能會(huì)發(fā)生內(nèi)存泄漏的問(wèn)題。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-434930.html
如何防御 Threadlocal 內(nèi)存泄漏問(wèn)題
1. 可以自己調(diào)用 remove 方法將不要的數(shù)據(jù)移除避免內(nèi)存泄漏的問(wèn)題;
2. 每次在做 set 方法的時(shí)候會(huì)清除之前 key 為 null;
3. Threadlocal 為弱引用;
Threadlocal 采用弱引用而不是強(qiáng)引用
1. 如果 key 是為強(qiáng)引用: 當(dāng)我們現(xiàn)在將 ThreadLocal 的引用指向?yàn)?null,但是每個(gè)線程中有自己獨(dú)立
2. ThreadLocalMap 還一直在繼續(xù)持有該對(duì)象,但是我們 ThreadLocal 對(duì)象不會(huì)被回收,
3. 就會(huì)發(fā)生 ThreadLocal 內(nèi)存泄漏的問(wèn)題。
4. 如果 key 是為弱引用: 當(dāng)我們現(xiàn)在將 ThreadLocal 的引用指向?yàn)?null,Entry 中的 key 指向?yàn)?null,
5. 但是下次調(diào)用 set 方法的時(shí)候,會(huì)根據(jù)判斷如果 key 空的情況下,直接刪除,避免了 Entry 發(fā)生內(nèi)存泄漏的問(wèn)題。
6. 不管是用強(qiáng)引用還是弱引用都是會(huì)發(fā)生內(nèi)存泄漏的問(wèn)題。弱引用中不會(huì)發(fā)生 ThreadLocal 內(nèi)存泄漏的問(wèn)題。
7. 但是最終根本的原因 Threadlocal 內(nèi)存泄漏的問(wèn)題,產(chǎn)生于 ThreadLocalMap 與
我們當(dāng)前線程的生命周期一樣,如果沒(méi)有手動(dòng)的刪除的情況下,就有可能會(huì)發(fā)生內(nèi)存泄漏的問(wèn)題。
到了這里,關(guān)于01.java并發(fā)編程面試寶典的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!