Java程序員面試中的多線程問題

摘要:不少核心Java面試題來源於多線程(Multi-Threading)和集合框架(Collections Framework),理解核心線程概念時,嫺熟的實際經驗是必需的。這篇文章收集了 Java 線程方面一些典型的問題,這些問題常常被高級工程師所問到。

不少核心Java面試題來源於多線程(Multi-Threading)和集合框架(Collections Framework),理解核心線程概念時,嫺熟的實際經驗是必需的。這篇文章收集了 Java 線程方面一些典型的問題,這些問題常常被高級工程師所問到。 html

0.Java 中多線程同步是什麼? java

在多線程程序下,同步能控制對共享資源的訪問。若是沒有同步,當一個 Java 線程在修改一個共享變量時,另一個線程正在使用或者更新同一個變量,這樣容易致使程序出現錯誤的結果。 面試

1.解釋實現多線程的幾種方法? 數據庫

一 Java 線程能夠實現 Runnable 接口或者繼承 Thread 類來實現,當你打算多重繼承時,優先選擇實現 Runnable。 編程

2.Thread.start ()與 Thread.run ()有什麼區別? api

Thread.start ()方法(native)啓動線程,使之進入就緒狀態,當 cpu 分配時間該線程時,由 JVM 調度執行 run ()方法。 安全

3.爲何須要 run ()和 start ()方法,咱們能夠只用 run ()方法來完成任務嗎? 數據結構

咱們須要 run ()&start ()這兩個方法是由於 JVM 建立一個單獨的線程不一樣於普通方法的調用,因此這項工做由線程的 start 方法來完成,start 由本地方法實現,須要顯示地被調用,使用這倆個方法的另一個好處是任何一個對象均可以做爲線程運行,只要實現了 Runnable 接口,這就避免因繼承了 Thread 類而形成的 Java 的多繼承問題。 多線程

4.什麼是 ThreadLocal 類,怎麼使用它? 併發

ThreadLocal 是一個線程級別的局部變量,並不是「本地線程」。ThreadLocal 爲每一個使用該變量的線程提供了一個獨立的變量副本,每一個線程修改副本時不影響其它線程對象的副本(譯者注)。

下面是線程局部變量(ThreadLocal variables)的關鍵點:

一個線程局部變量(ThreadLocal variables)爲每一個線程方便地提供了一個單獨的變量。

ThreadLocal 實例一般做爲靜態的私有的(private static)字段出如今一個類中,這個類用來關聯一個線程。

當多個線程訪問 ThreadLocal 實例時,每一個線程維護 ThreadLocal 提供的獨立的變量副本。

經常使用的使用可在 DAO 模式中見到,當 DAO 類做爲一個單例類時,數據庫連接(connection)被每個線程獨立的維護,互不影響。(基於線程的單例)

ThreadLocal 難於理解,下面這些引用鏈接有助於你更好的理解它。

Good article on ThreadLocal on IBM DeveloperWorks 》、《理解 ThreadLocal》、《Managing data : Good example》、《Refer Java API Docs

5.何時拋出 InvalidMonitorStateException 異常,爲何?

調用 wait ()/notify ()/notifyAll ()中的任何一個方法時,若是當前線程沒有得到該對象的鎖,那麼就會拋出 IllegalMonitorStateException 的異常(也就是說程序在沒有執行對象的任何同步塊或者同步方法時,仍然嘗試調用 wait ()/notify ()/notifyAll ()時)。因爲該異常是 RuntimeExcpetion 的子類,因此該異常不必定要捕獲(儘管你能夠捕獲只要你願意).做爲 RuntimeException,此類異常不會在 wait (),notify (),notifyAll ()的方法簽名說起。

6.Sleep ()、suspend ()和 wait ()之間有什麼區別?

Thread.sleep ()使當前線程在指定的時間處於「非運行」(Not Runnable)狀態。線程一直持有對象的監視器。好比一個線程當前在一個同步塊或同步方法中,其它線程不能進入該塊或方法中。若是另外一線程調用了 interrupt ()方法,它將喚醒那個「睡眠的」線程。

注意:sleep ()是一個靜態方法。這意味着只對當前線程有效,一個常見的錯誤是調用t.sleep (),(這裏的t是一個不一樣於當前線程的線程)。即使是執行t.sleep (),也是當前線程進入睡眠,而不是t線程。t.suspend ()是過期的方法,使用 suspend ()致使線程進入停滯狀態,該線程會一直持有對象的監視器,suspend ()容易引發死鎖問題。

object.wait ()使當前線程出於「不可運行」狀態,和 sleep ()不一樣的是 wait 是 object 的方法而不是 thread。調用 object.wait ()時,線程先要獲取這個對象的對象鎖,當前線程必須在鎖對象保持同步,把當前線程添加到等待隊列中,隨後另外一線程能夠同步同一個對象鎖來調用 object.notify (),這樣將喚醒原來等待中的線程,而後釋放該鎖。基本上 wait ()/notify ()與 sleep ()/interrupt ()相似,只是前者須要獲取對象鎖。

7.在靜態方法上使用同步時會發生什麼事?

同步靜態方法時會獲取該類的「Class」對象,因此當一個線程進入同步的靜態方法中時,線程監視器獲取類自己的對象鎖,其它線程不能進入這個類的任何靜態同步方法。它不像實例方法,由於多個線程能夠同時訪問不一樣實例同步實例方法。

8.當一個同步方法已經執行,線程可以調用對象上的非同步實例方法嗎?

能夠,一個非同步方法老是能夠被調用而不會有任何問題。實際上,Java 沒有爲非同步方法作任何檢查,鎖對象僅僅在同步方法或者同步代碼塊中檢查。若是一個方法沒有聲明爲同步,即便你在使用共享數據 Java 照樣會調用,而不會作檢查是否安全,因此在這種狀況下要特別當心。一個方法是否聲明爲同步取決於臨界區訪問(critial section access),若是方法不訪問臨界區(共享資源或者數據結構)就不必聲明爲同步的。

下面有一個示例說明:Common 類有兩個方法 synchronizedMethod1()和 method1(),MyThread 類在獨立的線程中調用這兩個方法。

  
  1. public class Common {  
  2.    
  3. public synchronized void synchronizedMethod1() {  
  4. System.out.println ("synchronizedMethod1 called");  
  5. try {  
  6. Thread.sleep (1000);  
  7. } catch (InterruptedException e) {  
  8. e.printStackTrace ();  
  9. }  
  10. System.out.println ("synchronizedMethod1 done");  
  11. }  
  12. public void method1() {  
  13. System.out.println ("Method 1 called");  
  14. try {  
  15. Thread.sleep (1000);  
  16. } catch (InterruptedException e) {  
  17. e.printStackTrace ();  
  18. }  
  19. System.out.println ("Method 1 done");  
  20. }  
  
  1. public class MyThread extends Thread {  
  2. private int id = 0;  
  3. private Common common;  
  4.    
  5. public MyThread (String name, int no, Common object) {  
  6. super(name);  
  7. common = object;  
  8. id = no;  
  9. }  
  10.    
  11. public void run () {  
  12. System.out.println ("Running Thread" + this.getName ());  
  13. try {  
  14. if (id == 0) {  
  15. common.synchronizedMethod1();  
  16. } else {  
  17. common.method1();  
  18. }  
  19. } catch (Exception e) {  
  20. e.printStackTrace ();  
  21. }  
  22. }  
  23.    
  24. public static void main (String[] args) {  
  25. Common c = new Common ();  
  26. MyThread t1 = new MyThread ("MyThread-1", 0, c);  
  27. MyThread t2 = new MyThread ("MyThread-2", 1, c);  
  28. t1.start ();  
  29. t2.start ();  
  30. }  
  31. }  

這裏是程序的輸出:

  
  1. Running ThreadMyThread-1  
  2. synchronizedMethod1 called  
  3. Running ThreadMyThread-2  
  4. Method 1 called  
  5. synchronizedMethod1 done  
  6. Method 1 done 

 

結果代表即便 synchronizedMethod1()方法執行了,method1()也會被調用。

9.在一個對象上兩個線程能夠調用兩個不一樣的同步實例方法嗎?

不能,由於一個對象已經同步了實例方法,線程獲取了對象的對象鎖。因此只有執行完該方法釋放對象鎖後才能執行其它同步方法。看下面代碼示例很是清晰:Common 類有 synchronizedMethod1()和 synchronizedMethod2()方法,MyThread 調用這兩個方法。

  
  1. public class Common {  
  2. public synchronized void synchronizedMethod1() {  
  3. System.out.println ("synchronizedMethod1 called");  
  4. try {  
  5. Thread.sleep (1000);  
  6. } catch (InterruptedException e) {  
  7. e.printStackTrace ();  
  8. }  
  9. System.out.println ("synchronizedMethod1 done");  
  10. }  
  11.    
  12. public synchronized void synchronizedMethod2() {  
  13. System.out.println ("synchronizedMethod2 called");  
  14. try {  
  15. Thread.sleep (1000);  
  16. } catch (InterruptedException e) {  
  17. e.printStackTrace ();  
  18. }  
  19. System.out.println ("synchronizedMethod2 done");  
  20. }  
  
  1. public class MyThread extends Thread {  
  2. private int id = 0;  
  3. private Common common;  
  4.    
  5. public MyThread (String name, int no, Common object) {  
  6. super(name);  
  7. common = object;  
  8. id = no;  
  9. }  
  10.    
  11. public void run () {  
  12. System.out.println ("Running Thread" + this.getName ());  
  13. try {  
  14. if (id == 0) {  
  15. common.synchronizedMethod1();  
  16. } else {  
  17. common.synchronizedMethod2();  
  18. }  
  19. } catch (Exception e) {  
  20. e.printStackTrace ();  
  21. }  
  22. }  
  23.    
  24. public static void main (String[] args) {  
  25. Common c = new Common ();  
  26. MyThread t1 = new MyThread ("MyThread-1", 0, c);  
  27. MyThread t2 = new MyThread ("MyThread-2", 1, c);  
  28. t1.start ();  
  29. t2.start ();  
  30. }  

10.什麼是死鎖

死鎖就是兩個或兩個以上的線程被無限的阻塞,線程之間相互等待所需資源。這種狀況可能發生在當兩個線程嘗試獲取其它資源的鎖,而每一個線程又陷入無限等待其它資源鎖的釋放,除非一個用戶進程被終止。就 JavaAPI 而言,線程死鎖可能發生在一下狀況。

  • 當兩個線程相互調用 Thread.join ()
  • 當兩個線程使用嵌套的同步塊,一個線程佔用了另一個線程必需的鎖,互相等待時被阻塞就有可能出現死鎖。

11.什麼是線程餓死,什麼是活鎖?

線程餓死和活鎖雖然不想是死鎖同樣的常見問題,可是對於併發編程的設計者來講就像一次邂逅同樣。

當全部線程阻塞,或者因爲須要的資源無效而不能處理,不存在非阻塞線程使資源可用。JavaAPI 中線程活鎖可能發生在如下情形:

  • 當全部線程在程序中執行 Object.wait (0),參數爲 0 的 wait 方法。程序將發生活鎖直到在相應的對象上有線程調用 Object.notify ()或者 Object.notifyAll ()。
  • 當全部線程卡在無限循環中。

這裏的問題並不詳盡,我相信還有不少重要的問題並未說起,您認爲還有哪些問題應該包括在上面呢?歡迎在評論中分享任何形式的問題與建議。

相關文章
相關標籤/搜索