秒掛了!與快手無緣了....
大家好,我是小林。
今天分享一位同學(xué)快手Java后端面經(jīng),問的問題基礎(chǔ)比較多,可惜同學(xué)沒怎么準(zhǔn)備好,回答的不是很多,面完就秒掛了。
圖片
考察的知識,我給大家羅列一下:
- 操作系統(tǒng):進(jìn)程線程、上下文、中斷
- Java:JVM、HashMap、synchronized、線程池
- 數(shù)據(jù)結(jié)構(gòu):數(shù)組和鏈表
- 算法:合并k個(gè)有序鏈表
技術(shù)八股
進(jìn)程和線程區(qū)別是什么?
圖片
- 本質(zhì)區(qū)別:進(jìn)程是操作系統(tǒng)資源分配的基本單位,而線程是任務(wù)調(diào)度和執(zhí)行的基本單位
- 在開銷方面:每個(gè)進(jìn)程都有獨(dú)立的代碼和數(shù)據(jù)空間(程序上下文),程序之間的切換會有較大的開銷;線程可以看做輕量級的進(jìn)程,同一類線程共享代碼和數(shù)據(jù)空間,每個(gè)線程都有自己獨(dú)立的運(yùn)行棧和程序計(jì)數(shù)器(PC),線程之間切換的開銷小
- 穩(wěn)定性方面:進(jìn)程中某個(gè)線程如果崩潰了,可能會導(dǎo)致整個(gè)進(jìn)程都崩潰。而進(jìn)程中的子進(jìn)程崩潰,并不會影響其他進(jìn)程。
- 內(nèi)存分配方面:系統(tǒng)在運(yùn)行的時(shí)候會為每個(gè)進(jìn)程分配不同的內(nèi)存空間;而對線程而言,除了CPU外,系統(tǒng)不會為線程分配內(nèi)存(線程所使用的資源來自其所屬進(jìn)程的資源),線程組之間只能共享資源
- 包含關(guān)系:沒有線程的進(jìn)程可以看做是單線程的,如果一個(gè)進(jìn)程內(nèi)有多個(gè)線程,則執(zhí)行過程不是一條線的,而是多條線(線程)共同完成的;線程是進(jìn)程的一部分,所以線程也被稱為輕權(quán)進(jìn)程或者輕量級進(jìn)程
什么是上下文?
任務(wù)是交給 CPU 運(yùn)行的,那么在每個(gè)任務(wù)運(yùn)行前,CPU 需要知道任務(wù)從哪里加載,又從哪里開始運(yùn)行所以,操作系統(tǒng)需要事先幫 CPU 設(shè)置好 CPU 寄存器和程序計(jì)數(shù)器。
CPU 寄存器和程序計(jì)數(shù)是 CPU 在運(yùn)行任何任務(wù)前,所必須依賴的環(huán)境,這些環(huán)境就叫做 CPU 上下文。
CPU 上下文切換就是先把前一個(gè)任務(wù)的 CPU 上下文(CPU 寄存器和程序計(jì)數(shù)器)保存起來,然后加載新任務(wù)的上下文到這些寄存器和程序計(jì)數(shù)器,最后再跳轉(zhuǎn)到程序計(jì)數(shù)器所指的新位置,運(yùn)行新任務(wù)。
系統(tǒng)內(nèi)核會存儲保持下來的上下文信息,當(dāng)此任務(wù)再次被分配給 CPU 運(yùn)行時(shí),CPU 會重新加載這些上下文,這樣就能保證任務(wù)原來的狀態(tài)不受影響,讓任務(wù)看起來還是連續(xù)運(yùn)行。
上面說到所謂的「任務(wù)」,主要包含進(jìn)程、線程和中斷。所以,可以根據(jù)任務(wù)的不同,把 CPU 上下文切換分成:進(jìn)程上下文切換、線程上下文切換
進(jìn)程是由內(nèi)核管理和調(diào)度的,所以進(jìn)程的切換只能發(fā)生在內(nèi)核態(tài)。所以,進(jìn)程的上下文切換不僅包含了虛擬內(nèi)存、棧、全局變量等用戶空間的資源,還包括了內(nèi)核堆棧、寄存器等內(nèi)核空間的資源。
通常,會把交換的信息保存在進(jìn)程的 PCB,當(dāng)要運(yùn)行另外一個(gè)進(jìn)程的時(shí)候,我們需要從這個(gè)進(jìn)程的 PCB 取出上下文,然后恢復(fù)到 CPU 中,這使得這個(gè)進(jìn)程可以繼續(xù)執(zhí)行,如下圖所示:
圖片
線程共享進(jìn)程的虛擬內(nèi)存資源,但是線程也有自己的私有數(shù)據(jù),比如棧和寄存器等,這些在上下文切換時(shí)也是需要保存的。
線程上下文切換時(shí),虛擬內(nèi)存這些資源就保持不動,只需要切換線程的私有數(shù)據(jù)、寄存器等不共享的數(shù)據(jù)。
什么是中斷?
CPU停下當(dāng)前的工作任務(wù),去處理其他事情,處理完后回來繼續(xù)執(zhí)行剛才的任務(wù),這一過程便是中斷。
中斷分為外部中斷和內(nèi)部中斷:
- 外部中斷分為可屏蔽中斷和不可屏蔽中斷:
- 可屏蔽中斷:通過INTR線向CPU請求的中斷,主要來自外部設(shè)備如硬盤,打印機(jī),網(wǎng)卡等。此類中斷并不會影響系統(tǒng)運(yùn)行,可隨時(shí)處理,甚至不處理,所以名為可屏蔽。
- 不可屏蔽中斷:通過NMI線向CPU請求的中斷,如電源掉電,硬件線路故障等。這里不可屏蔽的意思不是不可以屏蔽,不建議屏蔽,而是問題太大,屏蔽不了,不能屏蔽的意思。注:INTR和NMI都是CPU的引腳
- 內(nèi)部中斷分為陷阱、故障、終止:
陷阱:是一種有意的,預(yù)先安排的異常事件,一般是在編寫程序時(shí)故意設(shè)下的陷阱指令,而后執(zhí)行到陷阱指令后,CPU將會調(diào)用特定程序進(jìn)行相應(yīng)的處理,處理結(jié)束后返回到陷阱指令的下一條指令。如系統(tǒng)調(diào)用,程序調(diào)試功能等。如printf函數(shù),最底層的實(shí)現(xiàn)中會有一條int 0x80指令,這就是一條陷阱指令,使用0x80號中斷進(jìn)行系統(tǒng)調(diào)用。
故障:故障是在引起故障的指令被執(zhí)行,但還沒有執(zhí)行結(jié)束時(shí),CPU檢測到的一類的意外事件。出錯(cuò)時(shí)交由故障處理程序處理,如果能處理修正這個(gè)錯(cuò)誤,就將控制返回到引起故障的指令即CPU重新執(zhí)這條指令。如果不能處理就報(bào)錯(cuò)。常見的故障為缺頁,當(dāng)CPU引用的虛擬地址對應(yīng)的物理頁不存在時(shí)就會發(fā)生故障。缺頁異常是能夠修正的,有著專門的缺頁處理程序,它會將缺失的物理頁從磁盤中重新調(diào)進(jìn)主存。而后再次執(zhí)行引起故障的指令時(shí)便能夠順利執(zhí)行了。
終止:執(zhí)行指令的過程中發(fā)生了致命錯(cuò)誤,不可修復(fù),程序無法繼續(xù)運(yùn)行,只能終止,通常會是一些硬件的錯(cuò)誤。終止處理程序不會將控制返回給原程序,而是直接終止原程序
數(shù)組和鏈表區(qū)別是什么?
- 訪問效率:數(shù)組可以通過索引直接訪問任何位置的元素,訪問效率高,時(shí)間復(fù)雜度為O(1),而鏈表需要從頭節(jié)點(diǎn)開始遍歷到目標(biāo)位置,訪問效率較低,時(shí)間復(fù)雜度為O(n)。
- 插入和刪除操作效率:數(shù)組插入和刪除操作可能需要移動其他元素,時(shí)間復(fù)雜度為O(n),而鏈表只需要修改指針指向,時(shí)間復(fù)雜度為O(1)。
- 緩存命中率:由于數(shù)組元素在內(nèi)存中連續(xù)存儲,可以提高CPU緩存的命中率,而鏈表節(jié)點(diǎn)不連續(xù)存儲,可能導(dǎo)致CPU緩存的命中率較低,頻繁的緩存失效會影響性能。
- 應(yīng)用場景:數(shù)組適合靜態(tài)大小、頻繁訪問元素的場景,而鏈表適合動態(tài)大小、頻繁插入、刪除操作的場景
為什么數(shù)組查詢的復(fù)雜度為O(1)?
數(shù)組必須要內(nèi)存中一塊連續(xù)的空間,并且數(shù)組中必須存放相同的數(shù)據(jù)類型。 比如我們創(chuàng)建一個(gè)長度為 10,數(shù)據(jù)類型為整型的數(shù)組,在內(nèi)存中的地址是從 1000 開始,那么它在內(nèi)存中的存儲格式如下。
圖片
由于每個(gè)整型數(shù)據(jù)占據(jù) 4 個(gè)字節(jié)的內(nèi)存空間,因此整個(gè)數(shù)組的內(nèi)存空間地址是 1000~1039,根據(jù)這個(gè),我們就可以輕易算出數(shù)組中每個(gè)數(shù)據(jù)的內(nèi)存下標(biāo)地址。 利用這個(gè)特性,我們只要知道了數(shù)組下標(biāo),也就是數(shù)據(jù)在數(shù)組中的位置,比如下標(biāo) 2,就可以計(jì)算得到這個(gè)數(shù)據(jù)在內(nèi)存中的位置 1008,從而對這個(gè)位置的數(shù)據(jù) 241 進(jìn)行快速讀寫訪問,時(shí)間復(fù)雜度為 O(1)。
JVM是什么?
JVM是 java 虛擬機(jī),主要工作是解釋自己的指令集(即字節(jié)碼)并映射到本地的CPU指令集和OS的系統(tǒng)調(diào)用。
JVM屏蔽了與操作系統(tǒng)平臺相關(guān)的信息,使得Java程序只需要生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼(字節(jié)碼),就可在多種平臺上不加修改的運(yùn)行,這也是Java能夠“一次編譯,到處運(yùn)行的”原因。
Java為什么是跨平臺的?
Java 能支持跨平臺,主要依賴于 JVM 關(guān)系比較大。
JVM也是一個(gè)軟件,不同的平臺有不同的版本。我們編寫的Java源碼,編譯后會生成一種 .class 文件,稱為字節(jié)碼文件。Java虛擬機(jī)就是負(fù)責(zé)將字節(jié)碼文件翻譯成特定平臺下的機(jī)器碼然后運(yùn)行。也就是說,只要在不同平臺上安裝對應(yīng)的JVM,就可以運(yùn)行字節(jié)碼文件,運(yùn)行我們編寫的Java程序。
而這個(gè)過程中,我們編寫的Java程序沒有做任何改變,僅僅是通過JVM這一”中間層“,就能在不同平臺上運(yùn)行,真正實(shí)現(xiàn)了”一次編譯,到處運(yùn)行“的目的。
JVM是一個(gè)”橋梁“,是一個(gè)”中間件“,是實(shí)現(xiàn)跨平臺的關(guān)鍵,Java代碼首先被編譯成字節(jié)碼文件,再由JVM將字節(jié)碼文件翻譯成機(jī)器語言,從而達(dá)到運(yùn)行Java程序的目的。
編譯的結(jié)果不是生成機(jī)器碼,而是生成字節(jié)碼,字節(jié)碼不能直接運(yùn)行,必須通過JVM翻譯成機(jī)器碼才能運(yùn)行。不同平臺下編譯生成的字節(jié)碼是一樣的,但是由JVM翻譯成的機(jī)器碼卻不一樣。
所以,運(yùn)行Java程序必須有JVM的支持,因?yàn)榫幾g的結(jié)果不是機(jī)器碼,必須要經(jīng)過JVM的再次翻譯才能執(zhí)行。即使你將Java程序打包成可執(zhí)行文件(例如 .exe),仍然需要JVM的支持。
跨平臺的是Java程序,不是JVM。JVM是用C/C++開發(fā)的,是編譯后的機(jī)器碼,不能跨平臺,不同平臺下需要安裝不同版本的JVM。
圖片
Java為什么既是編譯型也是解釋型的?
首先在Java經(jīng)過編譯之后生成字節(jié)碼文件,接下來進(jìn)入JVM中,就有兩個(gè)步驟編譯和解釋。 如下圖:
圖片
編譯性:
- Java源代碼首先被編譯成字節(jié)碼,JIT 會把編譯過的機(jī)器碼保存起來,以備下次使用。
解釋性:
- JVM中一個(gè)方法調(diào)用計(jì)數(shù)器,當(dāng)累計(jì)計(jì)數(shù)大于一定值的時(shí)候,就使用JIT進(jìn)行編譯生成機(jī)器碼文件。否則就是用解釋器進(jìn)行解釋執(zhí)行,然后字節(jié)碼也是經(jīng)過解釋器進(jìn)行解釋運(yùn)行的。
所以Java既是編譯型也是解釋性語言,默認(rèn)采用的是解釋器和編譯器混合的模式。
Python和Java區(qū)別是什么?
- Java是一種已編譯的編程語言,Java編譯器將源代碼編譯為字節(jié)碼,而字節(jié)碼則由Java虛擬機(jī)執(zhí)行
- python是一種解釋語言,翻譯時(shí)會在執(zhí)行程序的同時(shí)進(jìn)行翻譯。
了解HashMap嗎?
- 在 JDK 1.7 版本之前, HashMap 數(shù)據(jù)結(jié)構(gòu)是數(shù)組和鏈表,HashMap通過哈希算法將元素的鍵(Key)映射到數(shù)組中的槽位(Bucket)。如果多個(gè)鍵映射到同一個(gè)槽位,它們會以鏈表的形式存儲在同一個(gè)槽位上,因?yàn)殒湵淼牟樵儠r(shí)間是O(n),所以沖突很嚴(yán)重,一個(gè)索引上的鏈表非常長,效率就很低了。
- 所以在 JDK 1.8 版本的時(shí)候做了優(yōu)化,當(dāng)一個(gè)鏈表的長度超過8的時(shí)候就轉(zhuǎn)換數(shù)據(jù)結(jié)構(gòu),不再使用鏈表存儲,而是使用紅黑樹,查找時(shí)使用紅黑樹,時(shí)間復(fù)雜度O(log n),可以提高查詢性能,但是在數(shù)量較少時(shí),即數(shù)量小于6時(shí),會將紅黑樹轉(zhuǎn)換回鏈表。
圖片
HashMap插入元素過程?
圖片
HashMap HashMap的put()方法用于向HashMap中添加鍵值對。當(dāng)調(diào)用HashMap的put()方法時(shí),會按照以下詳細(xì)流程執(zhí)行:
第一步:根據(jù)要添加的鍵的哈希碼計(jì)算在數(shù)組中的位置(索引)。
第二步:檢查該位置是否為空(即沒有鍵值對存在)
- 如果為空,則直接在該位置創(chuàng)建一個(gè)新的Entry對象來存儲鍵值對。將要添加的鍵值對作為該Entry的鍵和值,并保存在數(shù)組的對應(yīng)位置。將HashMap的修改次數(shù)(modCount)加1,以便在進(jìn)行迭代時(shí)發(fā)現(xiàn)并發(fā)修改。
第三步:如果該位置已經(jīng)存在其他鍵值對,檢查該位置的第一個(gè)鍵值對的哈希碼和鍵是否與要添加的鍵值對相同?
- 如果相同,則表示找到了相同的鍵,直接將新的值替換舊的值,完成更新操作。
第四步:如果第一個(gè)鍵值對的哈希碼和鍵不相同,則需要遍歷鏈表或紅黑樹來查找是否有相同的鍵:
如果鍵值對集合是鏈表結(jié)構(gòu):
- 從鏈表的頭部開始逐個(gè)比較鍵的哈希碼和equals()方法,直到找到相同的鍵或達(dá)到鏈表末尾。
- 如果找到了相同的鍵,則使用新的值取代舊的值,即更新鍵對應(yīng)的值。
- 如果沒有找到相同的鍵,則將新的鍵值對添加到鏈表的頭部。
如果鍵值對集合是紅黑樹結(jié)構(gòu):
- 在紅黑樹中使用哈希碼和equals()方法進(jìn)行查找。根據(jù)鍵的哈希碼,定位到紅黑樹中的某個(gè)節(jié)點(diǎn),然后逐個(gè)比較鍵,直到找到相同的鍵或達(dá)到紅黑樹末尾。
- 如果找到了相同的鍵,則使用新的值取代舊的值,即更新鍵對應(yīng)的值。
- 如果沒有找到相同的鍵,則將新的鍵值對添加到紅黑樹中。
第五步:檢查鏈表長度是否達(dá)到閾值(默認(rèn)為8):
- 如果鏈表長度超過閾值,且HashMap的數(shù)組長度大于等于64,則會將鏈表轉(zhuǎn)換為紅黑樹,以提高查詢效率。
第六步:檢查負(fù)載因子是否超過閾值(默認(rèn)為0.75):
- 如果鍵值對的數(shù)量(size)與數(shù)組的長度的比值大于閾值,則需要進(jìn)行擴(kuò)容操作。
第七步:擴(kuò)容操作:
- 創(chuàng)建一個(gè)新的兩倍大小的數(shù)組。
- 將舊數(shù)組中的鍵值對重新計(jì)算哈希碼并分配到新數(shù)組中的位置。
- 更新HashMap的數(shù)組引用和閾值參數(shù)。
第八步:完成添加操作。
需要注意的是,HashMap中的鍵和值都可以為null。此外,HashMap是非線程安全的,如果在多線程環(huán)境下使用,需要采取額外的同步措施或使用線程安全的ConcurrentHashMap。
重寫HashMap的equal和hashcode方法需要注意什么?
HashMap使用Key對象的hashCode()和equals()方法去決定key-value對的索引。當(dāng)我們試著從HashMap中獲取值的時(shí)候,這些方法也會被用到。如果這些方法沒有被正確地實(shí)現(xiàn),在這種情況下,兩個(gè)不同Key也許會產(chǎn)生相同的hashCode()和equals()輸出,HashMap將會認(rèn)為它們是相同的,然后覆蓋它們,而非把它們存儲到不同的地方。
同樣的,所有不允許存儲重復(fù)數(shù)據(jù)的集合類都使用hashCode()和equals()去查找重復(fù),所以正確實(shí)現(xiàn)它們非常重要。equals()和hashCode()的實(shí)現(xiàn)應(yīng)該遵循以下規(guī)則:
- 如果o1.equals(o2),那么o1.hashCode() == o2.hashCode()總是為true的。
- 如果o1.hashCode() == o2.hashCode(),并不意味著o1.equals(o2)會為true。
重寫HashMap的equal方法不當(dāng)會出現(xiàn)什么問題?
HashMap在比較元素時(shí),會先通過hashCode進(jìn)行比較,相同的情況下再通過equals進(jìn)行比較。
所以 equals相等的兩個(gè)對象,hashCode一定相等。hashCode相等的兩個(gè)對象,equals不一定相等(比如散列沖突的情況)
重寫了equals方法,不重寫hashCode方法時(shí),可能會出現(xiàn)equals方法返回為true,而hashCode方法卻返回false,這樣的一個(gè)后果會導(dǎo)致在hashmap等類中存儲多個(gè)一模一樣的對象,導(dǎo)致出現(xiàn)覆蓋存儲的數(shù)據(jù)的問題,這與hashmap只能有唯一的key的規(guī)范不符合,
列舉HashMap在多線程下可能會出現(xiàn)的問題?
- JDK1.7中的 HashMap 使用頭插法插入元素,在多線程的環(huán)境下,擴(kuò)容的時(shí)候有可能導(dǎo)致環(huán)形鏈表的出現(xiàn),形成死循環(huán)。因此,JDK1.8使用尾插法插入元素,在擴(kuò)容時(shí)會保持鏈表元素原本的順序,不會出現(xiàn)環(huán)形鏈表的問題。
- 多線程同時(shí)執(zhí)行 put 操作,如果計(jì)算出來的索引位置是相同的,那會造成前一個(gè) key 被后一個(gè) key 覆蓋,從而導(dǎo)致元素的丟失。此問題在JDK 1.7和 JDK 1.8 中都存在。
synchronized關(guān)鍵字的底層原理是什么?
synchronized是java提供的原子性內(nèi)置鎖,這種內(nèi)置的并且使用者看不到的鎖也被稱為監(jiān)視器鎖,使用synchronized之后,會在編譯之后在同步的代碼塊前后加上monitorenter和monitorexit字節(jié)碼指令,他依賴操作系統(tǒng)底層互斥鎖實(shí)現(xiàn)。他的作用主要就是實(shí)現(xiàn)原子性操作和解決共享變量的內(nèi)存可見性問題。
執(zhí)行monitorenter指令時(shí)會嘗試獲取對象鎖,如果對象沒有被鎖定或者已經(jīng)獲得了鎖,鎖的計(jì)數(shù)器+1。此時(shí)其他競爭鎖的線程則會進(jìn)入等待隊(duì)列中。
執(zhí)行monitorexit指令時(shí)則會把計(jì)數(shù)器-1,當(dāng)計(jì)數(shù)器值為0時(shí),則鎖釋放,處于等待隊(duì)列中的線程再繼續(xù)競爭鎖。
synchronized是排它鎖,當(dāng)一個(gè)線程獲得鎖之后,其他線程必須等待該線程釋放鎖后才能獲得鎖,而且由于Java中的線程和操作系統(tǒng)原生線程是一一對應(yīng)的,線程被阻塞或者喚醒時(shí)時(shí)會從用戶態(tài)切換到內(nèi)核態(tài),這種轉(zhuǎn)換非常消耗性能。
從內(nèi)存語義來說,加鎖的過程會清除工作內(nèi)存中的共享變量,再從主內(nèi)存讀取,而釋放鎖的過程則是將工作內(nèi)存中的共享變量寫回主內(nèi)存。
實(shí)際上大部分時(shí)候我認(rèn)為說到monitorenter就行了,但是為了更清楚的描述,還是再具體一點(diǎn)。
如果再深入到源碼來說,synchronized實(shí)際上有兩個(gè)隊(duì)列waitSet和entryList。
- 當(dāng)多個(gè)線程進(jìn)入同步代碼塊時(shí),首先進(jìn)入entryList
- 有一個(gè)線程獲取到monitor鎖后,就賦值給當(dāng)前線程,并且計(jì)數(shù)器+1
- 如果線程調(diào)用wait方法,將釋放鎖,當(dāng)前線程置為null,計(jì)數(shù)器-1,同時(shí)進(jìn)入waitSet等待被喚醒,調(diào)用notify或者notifyAll之后又會進(jìn)入entryList競爭鎖
- 如果線程執(zhí)行完畢,同樣釋放鎖,計(jì)數(shù)器-1,當(dāng)前線程置為null
圖片
線程池了解嗎?有哪些常見參數(shù)?
線程池是為了減少頻繁的創(chuàng)建線程和銷毀線程帶來的性能損耗。線程池的構(gòu)造函數(shù)有7個(gè)參數(shù):
圖片
- corePoolSize:線程池核心線程數(shù)量。默認(rèn)情況下,線程池中線程的數(shù)量如果 <= corePoolSize,那么即使這些線程處于空閑狀態(tài),那也不會被銷毀。
- maximumPoolSize:線程池中最多可容納的線程數(shù)量。當(dāng)一個(gè)新任務(wù)交給線程池,如果此時(shí)線程池中有空閑的線程,就會直接執(zhí)行,如果沒有空閑的線程且當(dāng)前線程池的線程數(shù)量小于corePoolSize,就會創(chuàng)建新的線程來執(zhí)行任務(wù),否則就會將該任務(wù)加入到阻塞隊(duì)列中,如果阻塞隊(duì)列滿了,就會創(chuàng)建一個(gè)新線程,從阻塞隊(duì)列頭部取出一個(gè)任務(wù)來執(zhí)行,并將新任務(wù)加入到阻塞隊(duì)列末尾。如果當(dāng)前線程池中線程的數(shù)量等于maximumPoolSize,就不會創(chuàng)建新線程,就會去執(zhí)行拒絕策略。
- keepAliveTime:當(dāng)線程池中線程的數(shù)量大于corePoolSize,并且某個(gè)線程的空閑時(shí)間超過了keepAliveTime,那么這個(gè)線程就會被銷毀。
- unit:就是keepAliveTime時(shí)間的單位。
- workQueue:工作隊(duì)列。當(dāng)沒有空閑的線程執(zhí)行新任務(wù)時(shí),該任務(wù)就會被放入工作隊(duì)列中,等待執(zhí)行。
- threadFactory:線程工廠??梢杂脕斫o線程取名字等等
- handler:拒絕策略。當(dāng)一個(gè)新任務(wù)交給線程池,如果此時(shí)線程池中有空閑的線程,就會直接執(zhí)行,如果沒有空閑的線程,就會將該任務(wù)加入到阻塞隊(duì)列中,如果阻塞隊(duì)列滿了,就會創(chuàng)建一個(gè)新線程,從阻塞隊(duì)列頭部取出一個(gè)任務(wù)來執(zhí)行,并將新任務(wù)加入到阻塞隊(duì)列末尾。如果當(dāng)前線程池中線程的數(shù)量等于maximumPoolSize,就不會創(chuàng)建新線程,就會去執(zhí)行拒絕策略。
線程池工作隊(duì)列滿了有哪些拒接策略?
當(dāng)線程池的任務(wù)隊(duì)列滿了之后,線程池會執(zhí)行指定的拒絕策略來應(yīng)對,常用的四種拒絕策略包括:CallerRunsPolicy、AbortPolicy、DiscardPolicy、DiscardOldestPolicy,此外,還可以通過實(shí)現(xiàn)RejectedExecutionHandler接口來自定義拒絕策略。
四種預(yù)置的拒絕策略:
- CallerRunsPolicy,使用線程池的調(diào)用者所在的線程去執(zhí)行被拒絕的任務(wù),除非線程池被停止或者線程池的任務(wù)隊(duì)列已有空缺。
- AbortPolicy,直接拋出一個(gè)任務(wù)被線程池拒絕的異常。
- DiscardPolicy,不做任何處理,靜默拒絕提交的任務(wù)。
- DiscardOldestPolicy,拋棄最老的任務(wù),然后執(zhí)行該任務(wù)。
- 自定義拒絕策略,通過實(shí)現(xiàn)接口可以自定義任務(wù)拒絕策略。
有線程池參數(shù)設(shè)置的經(jīng)驗(yàn)嗎?
- CPU密集型:corePoolSize = CPU核數(shù) + 1
- IO密集型:corePoolSize = CPU核數(shù) * 2
手撕算法
- 合并k個(gè)有序鏈表(面試官說給我出個(gè)簡單題)