JavaRush /Java Blog /Random-TW /翻譯:按主題排列的前 50 個面試問題。第1部分。
KapChook
等級 19
Volga

翻譯:按主題排列的前 50 個面試問題。第1部分。

在 Random-TW 群組發布
原文第一部分翻譯為新手、經驗豐富的程式設計師的 Top 50 Java 執行緒面試問題答案。 第二部分。 注意:這篇文章很大,這就是為什麼它不適合一個主題。另外,很複雜,我盡力去google了,但還是不行。因此,我們要求英語流利的參與者看原文並與翻譯進行比較,以防他們誤解或翻譯錯誤。先感謝您。 在任何面試中,無論是高級還是初級、經驗豐富還是初學者,您都會面臨一些有關線程、並行性和多線程的問題。事實上,這種內建的並發支援是 Java 的最大優勢之一,並幫助它在企業家和程式設計師中廣受歡迎。大多數利潤豐厚的 Java 開發人員職位都需要出色的多執行緒技能以及開發、調試和調整高效能、低延遲應用程式的經驗。因此,它是面試中最受歡迎的技能之一。在典型的Java面試中,面試官慢慢地從線程的基本概念開始,問一些問題,例如為什麼需要線程、如何創建線程、哪種創建方式更好、繼承Thread還是實現Runnable等,然後慢慢移動討論並發的困難、開發並行應用程式遇到的困難、JDK 1.5中引入的高級並發實用程式、平行應用程式的原理和設計模式以及經典的多執行緒問題。僅僅了解多執行緒的基礎知識是不夠的,還必須知道如何處理並發問題,例如死鎖、競爭條件、記憶體不一致以及各種執行緒安全問題。這些技能經過了徹底的測試,提出了各種多執行緒和並發挑戰。許多Java開發人員通常只是在面試之前閱讀問題,這並不是一件壞事,但你應該理解這一點。另外,累積問題並做同樣的練習會浪費很多時間,所以我創建了這個清單。
  1. Java中什麼是執行緒?

  2. 執行緒是一條獨立的執行路徑。其目標是利用機器中可用的多個處理器。透過使用多個線程,您可以加快 CPU 密集型任務的速度。例如,如果一個執行緒需要 100 毫秒來完成一項作業,則可以使用 10 個執行緒將該作業縮短至 10 毫秒。Java 在語言層面上提供了對多執行緒的出色支持,而且這是它最強大的優勢之一。
  3. Java中執行緒和行程的差別?

  4. 執行緒是行程的子集;換句話說,一個行程可以包含多個執行緒。兩個進程運行在不同的記憶體空間,但所有執行緒共享相同的記憶體空間。不要將其與堆疊記憶體混淆,堆疊記憶體對於每個執行緒來說都是不同的,用於儲存該執行緒的本地資料。
  5. 如何創建線程?

  6. 在語言層面,有兩種創建線程的方法。java.lang.Thread類別的物件代表一個線程,但它需要一個任務來運行,該任務是實作了java.lang.Runnable介面的物件。由於 Thread 類別實作了 Runnable 接口,因此您可以透過從 Thread 衍生類別或在其中實作 Runnable 介面來重寫 run() 方法。
  7. 什麼時候使用Runnable,什麼時候使用Thread?

  8. 這是對上一個問題的補充。我們知道,執行緒可以透過繼承Thread類別或實作Runnable介面來建立。那麼問題來了,哪種方法比較好,什麼時候用哪一種方法?如果您知道 Java 不支援多類繼承但允許實作多個接口,那麼這個問題很容易回答。這意味著如果您想從另一個類別繼承,最好實作 Runnable。
  9. start() 和 run() 方法之間的差異?

  10. 過去的棘手問題之一,但它仍然足以區分對 Java 中多執行緒的膚淺理解。start()方法用來啟動一個新執行緒。儘管start()在內部呼叫了run()方法,但它與簡單地呼叫run()並不相同。如果你像普通方法一樣調用 run() ,它會在同一個線程上調用,並且不會啟動新線程,這就是調用 start() 方法時發生的情況。
  11. 可運行和可調用之間的區別?

  12. 兩個介面都表示要在單獨的執行緒中執行的任務。Runnable 從 JDK 1.0 就已經存在,Callable 是在 JDK 1.5 中加入的。主要差異在於Callable的call()方法可以傳回值並拋出異常,而Runnable的run()方法則無法做到這一點。Callable 傳回一個可以包含計算結果的 Future 物件。
  13. CyclicBarrier 和 CountDownLatch 之間的差異?

  14. 雖然這兩個同步器都允許執行緒相互等待,但它們之間的主要區別在於,在計數器達到零後,您無法重複使用 CountDownLatch,但即使在屏障破壞後,您也可以再次使用 CyclicBarrier。
  15. Java記憶體模型是什麼?

  16. 記憶體模型是一組規則和指南,允許 Java 程式在多個記憶體、處理器和作業系統體系結構上確定性地運行。這對於多絲的情況尤其重要。記憶體模型保證了一個線程所做的更改對其他線程可見,其中之一就是發生之前關係。這種關係定義了一些規則,允許程式設計師預測和確定並行程式的行為。例如,發生在保證之前:
    • 執行緒中的每個操作都發生在該執行緒中按程序順序執行的每個操作之前,也稱為程序順序規則。
    • 解鎖監視器發生在同一監視器的每個後續鎖定之前,也稱為監視器鎖定規則。
    • 對易失性欄位的寫入發生在該欄位的每次後續讀取之前,這是易失性變數規則。
    • 執行緒上對 Thread.start() 的呼叫發生在任何其他執行緒注意到該執行緒已停止之前,或在 Thread.join() 成功之後,或如果 Thread.isAlive() 傳回 false,則 Thread.start() 規則。
    • 執行緒被另一個執行緒中斷發生在被中斷執行緒注意到中斷(透過拋出 InterruptedException 或透過檢查 isInterrupted())(執行緒的中斷規則)之前。
    • 物件建構函數的結束發生在該物件的終結器開始之前,即終結器規則。
    • 如果A發生在B之前,B發生在C之前,那麼A發生在C之前,這意味著happens-before保證了傳遞性。
  17. 什麼是易失性變數?

  18. 易失性是一種特殊的修飾符,只能應用於屬性。在平行 Java 程式中,如果沒有同步器,不同執行緒對屬性所做的變更對其他人來說是不可見的。Volatile 變數確保寫入發生在後續讀取之前,如上一個問題中的 Volatile 變數規則所述。
  19. 什麼是線程安全?Vector 類別安全嗎?

  20. 線程安全是物件或程式碼的屬性,可確保當由多個執行緒執行或使用時,程式碼將按預期運行。例如,如果在多個執行緒中使用相同的計數器實例,則執行緒安全計數器將不會跳過任何計數。顯然,集合類可以分為兩類,線程安全的和非線程安全的。Vector 是線程安全的,並透過同步更改 Vector 狀態的方法來實現這一點,另一方面,它的對應項 ArrayList 不是線程安全的。
  21. 什麼是競爭條件?

  22. 競爭條件是造成細微錯誤的原因。顧名思義,競爭條件是由於多個執行緒之間的競爭而發生的;如果應該第一個執行的執行緒輸掉了競爭,而第二個執行緒被執行,則程式碼的行為會發生變化,從而導致非確定性錯誤。由於線程之間競爭的混亂性質,這些是一些最難捕獲和重現的錯誤。競爭條件的一個例子是不穩定的執行。
  23. 如何停止執行緒?

  24. 我一直說 Java 為一切提供了豐富的 API,但諷刺的是,它並沒有提供停止執行緒的便捷方法。JDK 1.0 有幾種控制方法,例如stop()、suspend() 和resume(),由於潛在的死鎖威脅,這些方法在未來版本中被標記為已棄用;從那時起,Java API 開發人員就不再嘗試提供線程-抵抗 - 一種安全而優雅的方式來停止線程​​。程式設計師主要依賴這樣一個事實:執行緒一旦執行完 run() 或 call() 方法就會自行停止。要手動停止,程式設計師可以利用 易失性布林變量,並在每次迭代中檢查其值(如果 run() 方法中存在循環),或者使用 Interrupt() 方法中斷線程以突然取消作業。
  25. 當執行緒拋出異常時會發生什麼?

  26. 這是那些很好的技巧問題之一。簡單來說,如果未捕獲異常,則線程已死亡;如果安裝了未捕獲異常的處理程序,它將收到回調。Thread.UncaughtExceptionHandler 是一個接口,定義為嵌套接口,用於在線程由於未捕獲的異常而突然停止時調用的處理程序。當執行緒因未捕獲的異常而即將終止時,JVM 將使用 Thread.getUncaughtExceptionHandler() 檢查 UncaughtExceptionHandler 是否存在,並呼叫處理程序的 uncaughtException() 方法,將執行緒和異常傳遞為參數。
  27. 如何在兩個執行緒之間共享資料?

  28. 您可以使用共享物件或並行資料結構(例如 BlockingQueue)在執行緒之間共享資料。
  29. 通知和notifyAll之間的區別?

  30. 這是另一個棘手的問題,由於一個監視器可以由多個執行緒監視,因此 Java API 開發人員提供了一種方法來只通知一個或所有執行緒其狀態發生變化,但他們只提供了一半的實作。notify() 方法無法選擇特定線程,因此僅當您確定只有一個線程正在等待時它才有用。另一方面,notifyAll()通知所有執行緒並允許它們競爭監視器,這確保至少有一個執行緒向前移動。
  31. 為什麼wait、notify和notifyAll不在Thread類別中?

  32. 這是一個設計問題,測試候選人對現有系統的看法,或者他們是否曾經想到過類似的、乍看之下的東西。要回答這個問題,您需要提供幾個原因,說明為什麼這些方法在 Object 類別中可以更好地實現,而為什麼不在 Thread 類別中實現。第一個明顯的原因是 Java 支援物件層級的鎖,而不是執行緒層級的鎖。任何物件都有一個鎖,它由線程取得。如果一個執行緒需要等待某個鎖,那麼在物件上呼叫 wait() 比在該執行緒上呼叫 wait() 更有意義。如果 wait() 是在 Thread 類別中宣告的,那麼就不清楚執行緒正在等待哪個鎖。簡而言之,由於wait、notify和notifyAll是在鎖定層級操作的,因此在Object類別中聲明它們會更方便,因為lock指的是一個物件。
  33. 什麼是 ThreadLocal 變數?

  34. ThreadLocal 變數是 Java 程式設計師可以使用的一種特殊類型的變數。如狀態有狀態變數一樣,線程也有 ThreadLocal 變數。對於建立成本高昂的物件來說,這是實現線程安全的好方法;例如,您可以使用 ThreadLocal 使 SimpleDateFormat 成為線程安全的。由於這是一個昂貴的類,因此不建議在每次呼叫都需要單獨實例的本地範圍內使用它。透過為每個線程提供自己的副本,您可以一石二鳥。首先,透過使用新的固定數量的實例來減少昂貴物件的實例數量,其次,您可以在不丟失同步和不變性的情況下實現線程安全。執行緒局部變數的另一個很好的例子是 ThreadLocalRandom 類,它減少了多執行緒環境中創建成本高昂的 Random 物件的實例數量。
  35. 什麼是未來任務?

  36. FutureTask 是平行 Java 應用程式中可取消的非同步計算。此類提供了基本的 Future 實現,包括啟動和停止計算的方法、查詢計算狀態的方法以及檢索結果的方法。只有計算完成後才能得到結果;如果計算尚未完成,getter 方法將會阻塞。FutureTask 物件可用於包裝 Callable 和 Runnable 物件。由於FutureTask實作了Runnable,因此可以傳遞給Executor執行。
  37. 中斷和 isInterrupted 之間的區別?

  38. Interrupted() 和 isInterrupted() 之間的主要差異是前者重置中斷狀態,而後者則不重置。Java 中的中斷機制是使用稱為中斷狀態的內部標誌來實現的。透過呼叫 Thread.interrupt() 中斷執行緒會設定此標誌。當被中斷的執行緒透過呼叫靜態Thread.interrupted()方法檢查中斷狀態時,中斷狀態將會被重設。非靜態 isInterrupted() 方法由一個執行緒用來檢查另一個執行緒的中斷狀態,不會改變中斷標誌。按照慣例,任何透過拋出 InterruptedException 終止的方法都會重置中斷標誌。然而,如果另一個執行緒呼叫interrupt(),該標誌總是有可能立即被再次設定。
  39. 為什麼在同步區塊中呼叫wait和notify方法?

  40. 從靜態區塊或方法呼叫 wait 和 notification 的主要原因是 Java API 需要它。如果您從同步區塊外部呼叫它們,您的程式碼將拋出 IllegalMonitorStateException。一個更聰明的原因是避免等待和通知呼叫之間的競爭條件。
  41. 為什麼要循環檢查等待狀態?

  42. 如果等待執行緒不檢查循環中的等待狀態,則有可能會收到錯誤警告和錯誤喚醒調用,即使未達到該狀態,它也會直接退出。當等待線程醒來時,它不會考慮它正在等待的狀態可能仍然有效的事實。它實際上可能是過去的,但在呼叫notify()方法之後和執行緒喚醒之前發生了變化。因此,最好在循環內呼叫 wait()。
  43. 同步集合和並發集合之間的差異?

  44. 雖然同步和並發集合都提供線程安全集合,但後者更具可擴展性。在 Java 1.5 之前,程式設計師只能存取同步集合,當多個執行緒同時存取它們時,這會成為爭用的根源,從而難以擴展系統。Java 5 引入了並發集合(例如 ConcurrentHashMap),它不僅提供線程安全性,而且還使用鎖剝離和內部表分區等現代技術來提高可擴展性。
  45. 棧和堆的差別?

  46. 為什麼在有關多執行緒的問題中會出現這個問題?因為棧是一塊與執行緒密切相關的記憶體。每個執行緒都有自己的堆疊,其中儲存局部變數、方法參數和呼叫堆疊。儲存在一個執行緒堆疊上的變數對另一個執行緒不可見。另一方面,堆是所有執行緒共享的公共記憶體區域。對象,無論是本地還是任何其他級別,都是在堆上創建的。為了提高效能,執行緒通常將堆中的值快取到其堆疊上,這就是易失性變數發揮作用的地方。易失性告訴執行緒從主記憶體讀取變數。
  47. 什麼是執行緒池?

  48. 創建線程在時間和資源方面都是昂貴的。如果在處理請求時建立線程,則會減慢回應時間,並且進程只能建立有限數量的線程。為了避免這些問題,在應用程式啟動時會建立一個執行緒池,並重複使用這些執行緒來處理請求。這個執行緒池稱為“執行緒池”,其中的執行緒稱為工作執行緒。從Java 1.5開始,Java API提供了Executor框架,它允許你創建各種線程池,例如單一線程池,單位時間只處理一個作業,固定線程池,固定數量的線程池線程數、快取線程池、可擴展池。適合具有許多短期任務的應用程式。
  49. 如何解決生產者消費者問題?

  50. 您在現實中解決的大多數線程問題都來自生產者消費者模式,其中一個線程創建問題,第二個線程消耗它。您需要知道如何建立內部線程交互來解決這個問題。在低級別,您可以利用等待和通知方法,在高級別,您可以利用 Semaphore 或 BlockingQueue
  51. 如何避免死鎖?

  52. 翻譯:按主題排列的前 50 個面試問題。 第 1 部分 - 1 死鎖是一種狀態,其中一個執行緒正在等待第二個執行緒執行某些操作,而第二個執行緒同時也在等待第一個執行緒執行相同的操作。這是一個非常嚴重的問題,會導致您的程式凍結並且無法執行其設計目的。當達到這 4 種狀態時就會發生死鎖:
    • 互斥:不可分割模式下,至少必須佔用一種資源。在任何給定時間只有一個進程可以使用資源。
    • 持有並等待:進程至少持有一種資源,並請求其他進程持有的其他資源。
    • 無預先清理:如果資源已被佔用,作業系統不會重新分配資源,必須自願將資源分配給持有程序。
    • 循環等待:一個行程等待另一個行程釋放資源,而另一個行程又等待第一個行程釋放資源。
    避免死鎖的最簡單方法是避免循環等待;這可以透過按一定順序取得鎖並以相反順序釋放鎖來實現。
  53. 活鎖和死鎖的差別?

  54. 活鎖與死鎖類似,只是在活鎖中,所涉及的執行緒或進程的狀態不斷變化,相互依賴。活鎖是資源短缺的一種特殊情況。活鎖的一個真實例子是,當兩個人在狹窄的走廊上相遇時,每個人都試圖保持禮貌,退到一邊,因此他們不斷地從一邊走到另一邊。
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION