Java线程面试的前50个问题,面向初学者和经验丰富的程序员
您可以參加任何Java面試,無論是大四還是中級,經(jīng)驗(yàn)或新來的人,一定會看到線??程,并發(fā)和多線程中的幾個問題。 實(shí)際上,這種內(nèi)置的并發(fā)支持是Java編程語言的最強(qiáng)優(yōu)勢之一,并幫助它在企業(yè)界和程序員中同樣流行。 大多數(shù)有利可圖的Java開發(fā)人員職位都需要出色的Java多核核心技能,以及在開發(fā),調(diào)試和調(diào)整高性能低延遲并發(fā)Java應(yīng)用程序方面的經(jīng)驗(yàn)。
這就是原因,它是面試中最受歡迎的技能之一。 在典型的Java采訪中,Interviewer通過詢問以下問題逐漸從Thread的基本概念開始,例如您為什么需要線程,如何創(chuàng)建線程,這是一種更好的創(chuàng)建線程的方法,例如通過擴(kuò)展線程類或?qū)崿F(xiàn)Runnable ,然后慢慢進(jìn)入并發(fā)問題,并發(fā)Java應(yīng)用程序開發(fā)過程中面臨的挑戰(zhàn),Java內(nèi)存模型,JDK 1.5中引入的高級并發(fā)實(shí)用程序,并發(fā)Java應(yīng)用程序的原理和設(shè)計(jì)模式,經(jīng)典的多線程問題,例如生產(chǎn)者消費(fèi)者,就餐哲學(xué)家,讀者作家或簡單地有界緩沖區(qū)問題。 由于僅了解線程基礎(chǔ)知識還不夠,因此您必須知道如何處理并發(fā)問題,例如死鎖 , 競爭條件 ,內(nèi)存不一致以及各種與線程安全有關(guān)的問題。 通過提出各種多線程和并發(fā)問題,對這些技能進(jìn)行了全面測試。 許多Java開發(fā)人員習(xí)慣于在進(jìn)行面試之前僅閱讀和閱讀面試問題,這還不錯,但是您應(yīng)該相距不遠(yuǎn)。 同樣,收集問題并進(jìn)行相同的練習(xí)也很費(fèi)時間,這就是為什么我創(chuàng)建了這份清單,該清單是從各種采訪中收集的與Java多線程和并發(fā)相關(guān)的前50個問題 。 當(dāng)我發(fā)現(xiàn)它們時,我只會添加新的和最近的面試問題。 順便說一句,我還沒有在這里提供這個問題的答案,為什么? 因?yàn)槲蚁M蠖鄶?shù)Java開發(fā)人員都知道該問題的答案,如果不是,那么使用Google也可以廣泛獲得答案。 如果找不到任何特定問題的答案,可以隨時在評論部分詢問我。 您甚至可以在提供的鏈接上找到一些問題的答案,也可以在我之前的文章“帶答案的12個Java線程問題”中找到答案 。
這是我們有關(guān)Java線程,并發(fā)和多線程的主要問題列表。 您可以使用此列表為Java面試做好準(zhǔn)備。
線程是獨(dú)立的執(zhí)行路徑。 這是一種利用計(jì)算機(jī)中可用的多個CPU的方式。 通過使用多個線程,可以加快CPU綁定任務(wù)的速度。 例如,如果一個線程完成一項(xiàng)工作需要100毫秒,則可以使用10個線程將該任務(wù)減少到10毫秒。 Java在語言級別為多線程提供了出色的支持,并且它也是賣點(diǎn)之一。 有關(guān)更多詳細(xì)信息, 請參見此處 。
線程是進(jìn)程的子集,換句話說,一個進(jìn)程可以包含多個線程。 兩個進(jìn)程在不同的內(nèi)存空間上運(yùn)行,但是所有線程共享相同的內(nèi)存空間。 不要將此與堆棧內(nèi)存混淆,堆棧內(nèi)存對于不同的線程是不同的,并且用于將本地?cái)?shù)據(jù)存儲到該線程。 有關(guān)更多詳細(xì)信息,請參見此答案 。
在語言級別,有兩種方法可以用Java實(shí)現(xiàn)Thread。 java.lang.Thread的一個實(shí)例表示一個線程,但是它需要執(zhí)行一個任務(wù),它是接口java.lang.Runnable的實(shí)例。 由于Thread類本身實(shí)現(xiàn)Runnable,因此可以通過擴(kuò)展Thread類或僅實(shí)現(xiàn)Runnable接口來覆蓋run()方法。 有關(guān)詳細(xì)的答案和討論,請參見本文 。
這是先前的多線程面試問題的跟進(jìn)。 眾所周知,我們可以通過擴(kuò)展Thread類或?qū)崿F(xiàn)Runnable接口來實(shí)現(xiàn)線程,出現(xiàn)問題,哪種更好,什么時候使用? 如果您知道Java編程語言不支持類的多重繼承,但是可以使用它實(shí)現(xiàn)多個接口,那么這個問題將很容易回答。 這意味著,如果您還想擴(kuò)展另一個類(例如Canvas或CommandListener),則實(shí)現(xiàn)Runnable比擴(kuò)展Thread更好。 有關(guān)更多觀點(diǎn)和討論,您也可以參考這篇文章 。
早期的一個棘手的Java問題,但仍然足以區(qū)分對Java線程模型的淺淺理解start()方法用于啟動新創(chuàng)建的線程,而start()內(nèi)部調(diào)用run()方法,調(diào)用run有區(qū)別()方法直接。 當(dāng)您以常規(guī)方法調(diào)用run()時,該方法在同一線程中調(diào)用,因此不會啟動新線程,這是您調(diào)用start()方法時的情況。 閱讀此答案進(jìn)行更詳細(xì)的討論。
Runnable和Callable都表示打算在單獨(dú)的線程中執(zhí)行的任務(wù)。 JDK 1.0提供了Runnable,而JDK 1.5則添加了Callable。 兩者之間的主要區(qū)別在于Callable的call()方法可以返回值并引發(fā)Exception,而Runnable的run()方法則無法實(shí)現(xiàn)。 可調(diào)用的返回Future對象,可以保存計(jì)算結(jié)果。 有關(guān)此問題的更深入解答,請參閱我關(guān)于相同主題的博客文章 。
盡管CyclicBarrier和CountDownLatch都在一個或多個事件上等待線程數(shù),但是它們之間的主要區(qū)別是,一旦計(jì)數(shù)達(dá)到零,您就無法重用CountDownLatch,但是即使在屏障被破壞后,您也可以重用相同的CyclicBarrier。 有關(guān)其他幾點(diǎn)和示例代碼示例,請參見此答案 。
Java內(nèi)存模型是一組規(guī)則和準(zhǔn)則,可讓Java程序在多個內(nèi)存體系結(jié)構(gòu),CPU和操作系統(tǒng)之間確定性地運(yùn)行。 在多線程的情況下尤其重要。 Java內(nèi)存模型提供了某種保證,即一個線程所做的更改應(yīng)對其他線程可見,其中之一是發(fā)生在關(guān)系之前。 這種關(guān)系定義了一些規(guī)則,這些規(guī)則使程序員可以預(yù)期并推理并發(fā)Java程序的行為。 例如,之前發(fā)生的關(guān)系保證:
- 線程中的每個動作都發(fā)生-在該線程中的每個動作之后,它們在程序順序中排在后面,這稱為程序順序規(guī)則。
- 監(jiān)視器鎖發(fā)生解鎖-在同一監(jiān)視器鎖上的每個后續(xù)鎖之前,也稱為監(jiān)視器鎖規(guī)則。
- 在隨后每次對該字段的讀取之前,都會對volatile字段進(jìn)行寫操作,這稱為“可變變量規(guī)則”。
- 線程上對Thread.start的調(diào)用發(fā)生-在任何其他線程檢測到線程已終止之前,通過成功從Thread.join()返回或通過Thread.isAlive()返回false(也稱為線程啟動規(guī)則)來進(jìn)行。
- 一個線程在另一個線程上調(diào)用中斷發(fā)生-被中斷的線程在檢測到中斷之前(通過引發(fā)InterruptedException或調(diào)用isInterrupted或被中斷),通常稱為線程中斷規(guī)則。
- 對象構(gòu)造函數(shù)的結(jié)束發(fā)生在該對象的終結(jié)器開始之前,即終結(jié)器規(guī)則。
- 如果A發(fā)生在B之前,而B發(fā)生在C之前,那么A發(fā)生在C之前,這意味著在發(fā)生之前,保證了傳遞性。
我強(qiáng)烈建議閱讀《 Java并發(fā)實(shí)踐》第16章,以更詳細(xì)地了解Java內(nèi)存模型。
volatile是一個特殊的修飾符,只能與實(shí)例變量一起使用。 在并發(fā)Java程序中,在沒有任何同步器(例如,同步關(guān)鍵字或鎖)的情況下,其他人看不到多個線程對實(shí)例變量進(jìn)行的更改。 如前一個問題中的“易變變量規(guī)則”所述, 易變變量保證寫操作將在后續(xù)讀取之前發(fā)生。 閱讀此答案以了解有關(guān)volatile變量以及何時使用它們的更多信息。
(是的,請參閱詳細(xì)信息 )
線程安全性是對象或代碼的屬性,它保證如果多線程以任何方式(例如,讀寫)執(zhí)行或使用該線程或?qū)ο?#xff0c;其行為將與預(yù)期的一樣。 例如,如果在多個線程之間共享該計(jì)數(shù)器的同一實(shí)例,則線程安全計(jì)數(shù)器對象將不會丟失任何計(jì)數(shù)。 顯然,您也可以將集合類分為兩類,線程安全的和非線程安全的。 Vector確實(shí)是線程安全的類,它通過同步修改Vector狀態(tài)的方法來實(shí)現(xiàn)線程安全,另一方面,其對應(yīng)的ArrayList不是線程安全的。
當(dāng)Java程序處于并發(fā)執(zhí)行環(huán)境中時,爭用條件是一些細(xì)微的編程錯誤的原因。 顧名思義,競爭條件是由于多個線程之間的競爭而產(chǎn)生的,如果本應(yīng)首先執(zhí)行的線程丟失了競爭,然后執(zhí)行了第二,則代碼的行為會發(fā)生變化,這會成為不確定的錯誤。 由于線程之間競爭的隨機(jī)性,這是最難發(fā)現(xiàn)和再現(xiàn)的錯誤之一。 競爭條件的一個示例是亂序處理,有關(guān)Java程序中競爭條件的更多示例,請參見此答案 。
我總是說Java為所有事物提供了豐富的API,但具有諷刺意味的是Java沒有提供確定的停止線程的可靠方法。 JDK 1.0中有一些控制方法,例如stop(),suspend()和resume(),由于潛在的死鎖威脅而在以后的版本中不推薦使用,從那時起,Java API設(shè)計(jì)人員就一直沒有努力提供一致的,線程安全的和優(yōu)雅的方式來停止線程。 程序員主要依靠這樣的事實(shí),即線程一旦完成run()或call()方法的執(zhí)行就自動停止。 要手動停止,程序員可以利用易失的布爾變量,并在每次迭代中檢查run方法是否具有循環(huán)或中斷線程以突然取消任務(wù)。 有關(guān)在Java中停止線程的示例代碼,請參見本教程 。
這是我在訪談中看到的一個棘手的Java問題 。 簡而言之,如果未捕獲的線程將死亡,如果注冊了未捕獲的異常處理程序,則它將獲得回調(diào)。 Thread.UncaughtExceptionHandler是一個接口,定義為當(dāng)線程由于未捕獲的異常突然終止時調(diào)用的處理程序的嵌套接口。 當(dāng)線程由于未捕獲的異常而即將終止時,Java虛擬機(jī)將使用Thread.getUncaughtExceptionHandler()在線程中查詢其UncaughtExceptionHandler并將調(diào)用處理程序的uncaughtException()方法,并將線程和異常作為參數(shù)傳遞。
您可以使用共享對象或并發(fā)數(shù)據(jù)結(jié)構(gòu)(例如BlockingQueue)在線程之間共享數(shù)據(jù)。 請參閱本教程以學(xué)習(xí)Java中的線程間通信 。 它使用等待和通知方法來實(shí)現(xiàn)Producer使用者模式,該模式涉及在兩個線程之間共享對象。
這是Java核心訪談中另一個棘手的問題,因?yàn)槎鄠€線程可以等待單個監(jiān)視器鎖定,所以Java API設(shè)計(jì)器提供了一種方法,用于在等待條件改變后僅通知其中一個或所有通知,但是它們提供了一半的實(shí)現(xiàn)。 那里的notify()方法沒有提供任何選擇特定線程的方法,這就是為什么它僅在您知道只有一個線程正在等待時才有用。 另一方面,notifyAll()向所有線程發(fā)送通知,并允許它們競爭鎖,這確保了至少一個線程將繼續(xù)進(jìn)行。 有關(guān)更詳細(xì)的答案和代碼示例,請參見我在類似主題上的博客文章 。
這是一個與設(shè)計(jì)有關(guān)的問題,它檢查候選人對現(xiàn)有系統(tǒng)的看法,或者他是否曾經(jīng)想到過如此普遍但最初看起來不合適的事物。 為了回答這個問題,您必須給出一些原因,為什么這三個方法在Object類中有意義,而為什么不在Thread類中有意義。 顯而易見的原因之一是Java在對象級別而不是線程級別提供了鎖定。 每個對象都有鎖,該鎖由線程獲取。 現(xiàn)在,如果線程需要等待某些鎖定,則有必要在該對象而不是該線程上調(diào)用wait()。 如果在Thread類上聲明了wait()方法,則不清楚哪個線程正在等待。 簡而言之,由于wait,notify和notifyAll在鎖級別進(jìn)行操作,因此在對象類上定義它是有意義的,因?yàn)殒i屬于對象。 您也可以查看本文,以獲取有關(guān)此問題的更詳細(xì)的答案。
ThreadLocal變量是Java程序員可以使用的特殊類型的變量。 就像實(shí)例變量是每個實(shí)例一樣,ThreadLocal變量是每個線程。 這是一種實(shí)現(xiàn)昂貴創(chuàng)建對象的線程安全的好方法,例如,您可以使用ThreadLocal使SimpleDateFormat成為線程安全的。 由于該類很昂貴,因此在本地范圍內(nèi)使用它不是很好,因?yàn)槊看握{(diào)用都需要單獨(dú)的實(shí)例。 通過為每個線程提供自己的副本,您可以用一個箭頭射殺兩只鳥。 首先,通過重用固定數(shù)量的實(shí)例來減少昂貴對象的實(shí)例數(shù),其次,無需支付同步或不變性即可實(shí)現(xiàn)線程安全。 線程局部變量的另一個很好的例子是ThreadLocalRandom類,它減少了在多線程環(huán)境中創(chuàng)建昂貴的Random對象的實(shí)例數(shù)量。 請參閱此答案以了解有關(guān)Java中線程局部變量的更多信息。
FutureTask表示并發(fā)Java應(yīng)用程序中的可取消異步計(jì)算。 此類提供Future的基本實(shí)現(xiàn),其中包含啟動和取消計(jì)算,查詢以查看計(jì)算是否完成以及檢索計(jì)算結(jié)果的方法。 只有在計(jì)算完成后才能檢索結(jié)果; 如果計(jì)算尚未完成,則get方法將阻塞。 FutureTask對象可用于包裝Callable或Runnable對象。 從FutureTask開始
還實(shí)現(xiàn)了Runnable,可以將其提交給執(zhí)行器以執(zhí)行。
interrupted()和isInterrupted()之間的主要區(qū)別在于,前者清除中斷狀態(tài),而后者不清除。 Java多線程中的中斷機(jī)制是使用稱為中斷狀態(tài)的內(nèi)部標(biāo)志實(shí)現(xiàn)的。 通過調(diào)用Thread.interrupt()來中斷線程會設(shè)置此標(biāo)志。 當(dāng)被中斷的線程通過調(diào)用靜態(tài)方法 Thread.interrupted()檢查中斷時,將清除中斷狀態(tài)。 一個線程用于查詢另一線程的中斷狀態(tài)的非靜態(tài)isInterrupted()方法不會更改中斷狀態(tài)標(biāo)志。 按照慣例,任何通過拋出InterruptedException退出的方法都會清除中斷狀態(tài)。 但是,總是有可能通過另一個調(diào)用中斷的線程立即再次設(shè)置中斷狀態(tài)。
從同步塊或方法中調(diào)用wait和notify方法的主要原因是Java API強(qiáng)制了它。 如果不從同步上下文中調(diào)用它們,則代碼將引發(fā)IllegalMonitorStateException。 一個更微妙的原因是避免在等待和通知呼叫之間出現(xiàn)競爭狀態(tài)。 要了解更多信息,請?jiān)诖颂幉榭次业耐印?
等待中的線程可能會收到錯誤警報(bào)和虛假的喚醒調(diào)用,如果它不檢查循環(huán)中的等待條件,即使不滿足條件,它也只會退出。 這樣,當(dāng)?shù)却€程喚醒時,它不能假定其正在等待的狀態(tài)仍然有效。 它可能在過去是有效的,但是在調(diào)用notify()方法之后以及在等待線程喚醒之前,狀態(tài)可能已經(jīng)更改。 這就是從循環(huán)調(diào)用wait()方法總是更好的原因,您甚至可以創(chuàng)建用于調(diào)用wait的模板并在Eclipse中進(jìn)行通知。 要了解有關(guān)此問題的更多信息,建議您閱讀有關(guān)線程和同步的有效Java項(xiàng)目。
盡管同步和并發(fā)收集都提供了適用于多線程和并發(fā)訪問的線程安全收集,但是后期比以前更具可伸縮性。 在Java 1.5之前,Java程序員只有同步收集,如果多個線程同時訪問它們,它們將成為爭用的源,這會妨礙系統(tǒng)的可伸縮性。 Java 5引入了諸如ConcurrentHashMap之類的并發(fā)集合,該集合不僅提供線程安全性,而且還通過使用鎖剝離和對內(nèi)部表進(jìn)行分區(qū)等現(xiàn)代技術(shù)來提高可伸縮性。 有關(guān)Java中同步和并發(fā)收集之間的更多區(qū)別,請參見此答案 。
為什么有人將這個問題作為多線程和并發(fā)的一部分? 因?yàn)槎褩J桥c線程緊密相關(guān)的內(nèi)存區(qū)域。 為了回答這個問題,堆棧和堆都是Java應(yīng)用程序中的特定內(nèi)存。 每個線程都有自己的堆棧,該堆棧用于存儲局部變量,方法參數(shù)和調(diào)用堆棧。 存儲在一個線程堆棧中的變量對其他線程不可見。 另一方面,堆是所有線程共享的公共內(nèi)存區(qū)域。 在堆內(nèi)部創(chuàng)建本地或任何級別的對象。 為了提高性能,線程傾向于將值從堆中緩存到堆棧中,如果該變量被多個線程修改,則可能會產(chǎn)生問題,這就是易變變量的所在。 volatile建議線程始終從主內(nèi)存中讀取變量的值。 請參閱本文以了解有關(guān)Java中堆棧和堆的更多信息,以更詳細(xì)地回答此問題。
就時間和資源而言,創(chuàng)建線程是昂貴的。 如果在請求處理時創(chuàng)建線程,這會減慢響應(yīng)時間,并且一個進(jìn)程只能創(chuàng)建數(shù)量有限的線程。 為避免這兩個問題,在應(yīng)用程序啟動時將創(chuàng)建線程池,并將線程重用于請求處理。 該線程池稱為“線程池”,而線程稱為工作線程。 從JDK 1.5版本開始,Java API提供了Executor框架,該框架允許您創(chuàng)建不同類型的線程池,例如單個線程池(一次處理一個任務(wù)),固定線程池(固定數(shù)量的線程池)或緩存的線程池。 (一個可擴(kuò)展的線程池,適用于具有許多短期任務(wù)的應(yīng)用程序)。 請參閱本文以了解有關(guān)Java中線程池的更多信息,以準(zhǔn)備該問題的詳細(xì)答案。
您在現(xiàn)實(shí)世界中解決的大多數(shù)線程問題都屬于Producer使用者模式類別,其中一個線程正在執(zhí)行任務(wù),而另一個線程正在使用該線程。 您必須知道如何進(jìn)行線程間通信以解決此問題。 在最低級別上,您可以使用wait和notify來解決此問題,在最高級別上,您可以利用Semaphore或BlockingQueue來實(shí)現(xiàn)Producer使用者模式,如本教程所示。
死鎖是指兩個線程互相等待以采取行動,從而允許它們進(jìn)一步移動的情況。 這是一個嚴(yán)重的問題,因?yàn)榘l(fā)生這種情況時,您的程序會掛起,并且沒有執(zhí)行預(yù)期的任務(wù)。 為了使死鎖發(fā)生,必須滿足以下四個條件:
- 互斥:至少一種資源必須以不可共享的方式持有。 在任何給定時刻,只有一個進(jìn)程可以使用資源。
- 保留并等待:一個進(jìn)程當(dāng)前正在保存至少一個資源,并請求其他進(jìn)程正在保留的其他資源。
- 否搶占:一旦分配了資源,操作系統(tǒng)不得取消分配資源。 它們必須由持有程序自愿釋放。
- 循環(huán)等待:一個進(jìn)程必須等待另一個進(jìn)程正在占用的資源,而另一個進(jìn)程又在等待第一個進(jìn)程釋放該資源。
避免死鎖的最簡單方法是防止循環(huán)退出 ,這可以通過以特定順序獲取鎖并以相反的順序釋放鎖來完成,以便線程僅在持有另一個時才能繼續(xù)獲取鎖。 在本教程中查看實(shí)際的代碼示例,以及有關(guān)避免Java中死鎖的技術(shù)的詳細(xì)討論。
這個問題是前一次面試問題的延伸。 活鎖類似于死鎖,不同之處在于,活鎖中涉及的線程或進(jìn)程的狀態(tài)彼此之間不斷變化,而沒有任何一個進(jìn)一步發(fā)展。 Livelock是資源匱乏的特例。 當(dāng)兩個人在狹窄的走廊里相遇時,發(fā)生了現(xiàn)實(shí)生活中的活鎖例子,每個人都試圖通過移動到一邊讓對方通過而禮貌,但最終卻沒有任何進(jìn)展就左右搖擺,因?yàn)樗麄兌挤磸?fù)移動在同一時間相同的方式。 簡而言之,活動鎖和死鎖之間的主要區(qū)別在于,在先前的過程更改狀態(tài)下,沒有任何進(jìn)展。
我什至不知道您可以在Java訪談的電話回合中問到這個問題之前,我是否可以檢查線程是否已經(jīng)鎖定。 在java.lang.Thread上有一個名為holdLock()的方法,當(dāng)且僅當(dāng)當(dāng)前線程持有對指定對象的監(jiān)視器鎖時,它才返回true。 您也可以查看本文以獲得更詳細(xì)的答案 。
根據(jù)操作系統(tǒng)的不同,可以采取多種方式進(jìn)行Java進(jìn)程的線程轉(zhuǎn)儲。 進(jìn)行線程轉(zhuǎn)儲時,JVM會在日志文件或標(biāo)準(zhǔn)錯誤控制臺中轉(zhuǎn)儲所有線程的狀態(tài)。 在Windows中,您可以使用Ctrl + Break鍵組合進(jìn)行線程轉(zhuǎn)儲,而在Linux上,您可以使用kill -3命令。 您還可以使用名為jstack的工具進(jìn)行線程轉(zhuǎn)儲,它對進(jìn)程ID進(jìn)行操作,可以使用另一個名為jps的工具找到該進(jìn)程。
這是簡單的一個,-Xss參數(shù)用于控制Java中Thread的堆棧大小。 您可以查看此JVM選項(xiàng)列表,以了解有關(guān)此參數(shù)的更多信息。
曾經(jīng)有幾天,在Java中提供互斥的唯一方法是通過synced關(guān)鍵字,但是它有幾個缺點(diǎn),例如您不能將鎖擴(kuò)展到方法或塊邊界之外,不能放棄嘗試鎖等。Java5解決了這個問題通過Lock接口提供更復(fù)雜的控制來解決問題。 ReentrantLock是Lock接口的常見實(shí)現(xiàn),它提供與可同步方法和語句訪問的隱式監(jiān)視器鎖相同的基本行為和語義的可重入互斥鎖,但具有擴(kuò)展功能。 請參閱本文,以了解這些功能以及Java中Synchronized與ReentrantLock之間的更多區(qū)別。
多線程中的排序可以通過不同的方法來實(shí)現(xiàn),但是您可以簡單地使用線程類的join()方法在另一個線程完成執(zhí)行時啟動一個線程。 為了確保執(zhí)行三個線程,您需要首先啟動最后一個線程,例如T3,然后以相反的順序調(diào)用join方法,例如T3調(diào)用T2。 join,然后T2調(diào)用T1.join,這樣T1將首先完成,而T3將最后完成。 要了解有關(guān)join方法的更多信息,請參閱本教程 。
Yield方法是請求當(dāng)前線程放棄CPU以便其他線程可以執(zhí)行的一種方法。 Yield是一種靜態(tài)方法,僅保證當(dāng)前線程將放棄CPU,但未說明任何其他線程將獲得CPU。 同一線程可能會收回CPU并重新開始執(zhí)行。 請參閱本文,以了解有關(guān)屈服方法的更多信息并更好地回答此問題。
ConcurrentHashMap通過將實(shí)際映射劃分為多個部分來實(shí)現(xiàn)其可伸縮性和線程安全性。 使用并發(fā)級別可以實(shí)現(xiàn)此分區(qū)。 它是ConcurrentHashMap構(gòu)造函數(shù)的可選參數(shù),默認(rèn)值為16。表在內(nèi)部進(jìn)行了分區(qū),以嘗試允許指定數(shù)量的并發(fā)更新而不會發(fā)生爭用。 要了解有關(guān)并發(fā)級別和內(nèi)部調(diào)整大小的更多信息,請參閱我的文章ConcurrentHashMap如何在Java中工作 。
Java中的信號量是一種新型的同步器。 這是一個計(jì)數(shù)信號量。 從概念上講,信號量維護(hù)一組許可證。 如有必要,每個Acquisition()都會阻塞,直到獲得許可為止,然后再獲取許可。 每個release()添加一個許可,有可能釋放阻塞獲取者。 但是,沒有使用實(shí)際的許可對象。 信號量只是保持可用數(shù)量的計(jì)數(shù)并采取相應(yīng)措施。 信號量用于保護(hù)固定數(shù)量的可用昂貴資源,例如池中的數(shù)據(jù)庫連接。 請參閱本文,以了解有關(guān)在Java中對信號量進(jìn)行計(jì)數(shù)的更多信息。
這是我列表中另一個棘手的問題。 許多程序員會認(rèn)為它會阻塞直到任務(wù)被清除,但是它是正確的。 如果無法安排執(zhí)行任務(wù),則ThreadPoolExecutor的Submit()方法將拋出RejectedExecutionException。
兩種方法都是將任務(wù)提交到線程池的方法,但是兩者之間略有不同。 execute(Runnable command)在Executor接口中定義,將來會執(zhí)行給定的任務(wù),但更重要的是它不返回任何內(nèi)容。 它的返回類型為void。 另一方面,submit()是重載的方法,它可以執(zhí)行Runnable或Callable任務(wù),并且可以返回可以保存未決計(jì)算結(jié)果的Future對象。 此方法在ExecutorService接口上定義,該接口擴(kuò)展了Executor接口,并且其他所有線程池類(例如ThreadPoolExecutor或ScheduledThreadPoolExecutor)都將獲得這些方法。 要了解有關(guān)線程池的更多信息,可以查看本文 。
阻塞方法是一種阻塞直到任務(wù)完成的方法,例如ServerSocket的accept()方法阻塞直到連接了客戶端。 在這里阻塞意味著直到任務(wù)完成之前控制權(quán)不會返回給調(diào)用者。 另一方面,存在異步或非阻塞方法,該方法甚至在任務(wù)完成之前就返回。 要了解有關(guān)阻塞方法的更多信息,請參見此答案 。
您可以簡單地將這個問題表示為“不,Swing不是線程安全的”,但是即使面試官沒有詢問,您也必須解釋您的意思。 當(dāng)我們說swing不是線程安全的時,我們通常引用其組件,該組件不能在多個線程中進(jìn)行修改。 對GUI組件的所有更新都必須在AWT線程上完成,Swing提供了同步和異步回調(diào)方法來安排此類更新。 您也可以閱讀我的文章,以了解有關(guān)擺動和線程安全的更多信息,以更好地回答此問題。 甚至接下來的兩個問題也與此概念相關(guān)。
這是Swing API為Java開發(fā)人員提供的兩種方法,可從事件分發(fā)程序線程以外的線程更新GUI組件。 InvokeAndWait()同步更新GUI組件,例如進(jìn)度條,一旦取得進(jìn)度,也應(yīng)更新該條以反映該更改。 如果在另一個線程中跟蹤進(jìn)度,則必須調(diào)用invokeAndWait()來調(diào)度事件分派器線程對該組件的更新。 另一方面,invokeLater()是異步調(diào)用以更新組件。 您也可以參考此答案以獲取更多信息。
這個問題再次與擺動和線程安全有關(guān),盡管組件不是線程安全的,但是某些方法可以從多個線程安全地調(diào)用。 我知道repaint()和revalidate()是線程安全的,但是在不同的swing組件上還有其他方法,例如JTextComponent的setText()方法,JTextArea類的insert()和append()方法。
這個問題看似與多線程和并發(fā)無關(guān),但確實(shí)如此。 不變性有助于簡化Java中已經(jīng)很復(fù)雜的并發(fā)代碼。 由于不共享對象可以共享而無需任何同步,因此Java開發(fā)人員非常重視。 旨在在線程之間共享的核心值對象應(yīng)該是不變的,以提高性能和簡化性。 不幸的是,Java中沒有@Immutable批注,它可以使您的對象不可變,Java開發(fā)人員必須完成艱苦的工作。 您需要保留一些基本知識,例如在構(gòu)造函數(shù)中初始化狀態(tài),沒有setter方法,沒有引用泄漏,保留可變對象的單獨(dú)副本以創(chuàng)建Immutable對象。 有關(guān)逐步指南的信息,請參閱我的文章, 如何使對象在Java中不可變 。 這將為您提供足夠的材料來自信地回答這個問題。
通常,讀寫鎖是鎖剝離技術(shù)的結(jié)果,可以提高并發(fā)應(yīng)用程序的性能。 在Java中,ReadWriteLock是在Java 5版本中添加的接口。 ReadWriteLock維護(hù)一對關(guān)聯(lián)的鎖,一個用于只讀操作,一個用于寫入。 只要沒有寫程序,讀鎖就可以同時由多個讀程序線程保持。 寫鎖是排他的。 如果愿意,可以使用自己的一組規(guī)則來實(shí)現(xiàn)此接口,否則可以使用JDK隨附的ReentrantReadWriteLock,它最多支持65535遞歸寫鎖和65535讀鎖。
繁忙旋轉(zhuǎn)是并發(fā)程序員用來使線程在特定條件下等待的技術(shù)。 Unlike traditional methods eg wait(), sleep() or yield() which all involves relinquishing CPU control, this method does not relinquish CPU, instead it just runs empty loop. Why would someone do that? to preserve CPU caches. In multi core system, its possible for a paused thread to resume on different core, which means rebuilding cache again. To avoid cost of rebuilding cache, programmer prefer to wait for much smaller time doing busy spin. You can also see this answer to learn more about this question.
This is an interesting question for Java programmer, at first, volatile and atomic variable look very similar, but they are different. Volatile variable provides you happens-before guarantee that a write will happen before any subsequent write, it doesn't guarantee atomicity. For example count++ operation will not become atomic just by declaring count variable as volatile. On the other hand AtomicInteger class provides atomic method to perform such compound operation atomically eg getAndIncrement() is atomic replacement of increment operator. It can be used to atomically increment current value by one. Similarly you have atomic version for other data type and reference variable as well.
This is one more tricky question for average Java programmer, if he can bring the fact about whether lock is released or not is key indicator of his understanding. To answer this question, no matter how you exist synchronized block, either normally by finishing execution or abruptly by throwing exception, thread releases the lock it acquired while entering that synchronized block. This is actually one of the reason I like synchronized block over lock interface, which requires explicit attention to release lock, generally this is achieved by releasing lock in finally block .
This is one of the very popular question on Java interviews, and despite its popularity, chances of candidate answering this question satisfactory is only 50%. Half of the time, they failed to write code for double checked locking and half of the time they failed how it was broken and fixed on Java 1.5. This is actually an old way of creating thread-safe singleton, which tries to optimize performance by only locking when Singleton instance is created first time, but because of complexity and the fact it was broken for JDK 1.4, I personally don't like it. Anyway, even if you not prefer this approach its good to know from interview point of view. Since this question deserve a detailed answer, I have answered in a separate post, you can read my post how double checked locking on Singleton works to learn more about it.
This question is actually follow-up of previous question. If you say you don't like double checked locking then Interviewer is bound to ask about alternative ways of creating thread-safe Singleton class. There are actually man, you can take advantage of class loading and static variable initialization feature of JVM to create instance of Singleton, or you can leverage powerful enumeration type in Java to create Singleton. I actually preferred that way, you can also read this article to learn more about it and see some sample code.
This is my favourite question, because I believe that you must follow certain best practices while writing concurrent code which helps in performance, debugging and maintenance. Following are three best practices, I think an average Java programmer should follow:
- Always give meaningful name to your thread This goes a long way to find a bug or trace an execution in concurrent code. OrderProcessor, QuoteProcessor or TradeProcessor is much better than Thread-1. Thread-2 and Thread-3. Name should say about task done by that thread. All major framework and even JDK follow this best practice.
- Avoid locking or Reduce scope of Synchronization
Locking is costly and context switching is even more costlier. Try to avoid synchronization and locking as much as possible and at bare minimum, you should reduce critical section. That's why I prefer synchronized block over synchronized method, because it gives you absolute control on scope of locking. - Prefer Synchronizers over wait and notify
Synchronizers like CountDownLatch, Semaphore, CyclicBarrier or Exchanger simplifies coding. It's very difficult to implement complex control flow right using wait and notify. Secondly, these classes are written and maintained by best in business and there is good chance that they are optimized or replaced by better performance code in subsequent JDK releases. By using higher level synchronization utilities, you automatically get all these benefits. - Prefer Concurrent Collection over Synchronized Collection
This is another simple best practice which is easy to follow but reap good benefits. Concurrent collection are more scalable than their synchronized counterpart, that's why its better to use them while writing concurrent code. So next time if you need map, think about ConcurrentHashMap before thinking Hashtable. See my article Concurrent Collections in Java , to learn more about modern collection classes and how to make best use of them.
This question is like how do you force garbage collection in Java, their is no way, though you can make request using System.gc() but its not guaranteed. On Java multi-threading their is absolute no way to force start a thread, this is controlled by thread scheduler and Java exposes no API to control thread schedule. This is still a random bit in Java.
The fork join framework, introduced in JDK 7 is a powerful tool available to Java developer to take advantage of multiple processors of modern day servers. 它是為可以遞歸分解為較小部分的工作而設(shè)計(jì)的。 目標(biāo)是使用所有可用的處理能力來增強(qiáng)應(yīng)用程序的性能。 One significant advantage of The fork/join framework is that it uses a work-stealing algorithm. 工作用盡的工作線程可以從其他仍很忙的線程中竊取任務(wù)。 See this article for much more detailed answer of this question.
Though both wait and sleep introduce some form of pause in Java application, they are tool for different needs. Wait method is used for inter thread communication, it relinquish lock if waiting condition is true and wait for notification when due to action of another thread waiting condition becomes false. On the other hand sleep() method is just to relinquish CPU or stop execution of current thread for specified time duration. Calling sleep method doesn't release the lock held by current thread. You can also take look at this article to answer this question with more details.
That's all on this list of top 50 Java multi-threading and concurrency interview questions . I have not shared answers of all the questions but provided enough hints and links to explore further and find answers by yourselves. As I said, let me know if you don't find answer of any particular question and I will add answer here. You can use this list to not only to prepare for your core Java and programming interviews but also to check your knowledge about basics of threads, multi-threading, concurrency, design patterns and threading issues like race conditions, deadlock and thread safety problems. My intention is to make this list of question as mother of all list of Java Multi-threading questions, but this can not be done without your help. You can also share any question with us, which has been asked to you or any question for which you yet to find an answer. This master list is equally useful to Java developers of all levels of experience. You can read through this list even if you have 2 to 3 years of working experience as junior developer or 5 to 6 years as senior developer. It's even useful for freshers and beginners to expand their knowledge. I will add new and latest multi-threading question as and when I come across, and I request you all to ask, share and answer questions via comments to keep this list relevant for all Java programmers.
翻譯自: https://www.javacodegeeks.com/2014/07/top-50-java-thread-interview-questions-answers-for-freshers-experienced-programmers.html
總結(jié)
以上是生活随笔為你收集整理的Java线程面试的前50个问题,面向初学者和经验丰富的程序员的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring MVC集成测试:断言给定的
- 下一篇: Java中File的getPath(),