天天看點

java程式員面試被問到的經典多線程問題

很多核心Java面試題來源于多線程(Multi-Threading)和集合架構(Collections Framework),了解核心線程概念時,娴熟的實際經驗是必需的。這篇文章收集了 Java 線程方面一些典型的問題,這些問題經常被進階工程師所問到。

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

在多線程程式下,同步能控制對共享資源的通路。如果沒有同步,當一個 Java 線程在修改一個共享變量時,另外一個線程正在使用或者更新同一個變量,這樣容易導緻程式出現錯誤的結果。

1.解釋實作多線程的幾種方法?

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

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

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 類在獨立的線程中調用這兩個方法。

[java] 
   ​​ view plain​​
   ​​copy​​
   ​​print​​
   ​​?​​
  
 
 
1. package
2. /** 
3.  * @author zhuyong 
4.  * 建立時間:2012-5-29 上午10:51:53 
5.  *
6.  * 類說明 :
7.  */
8. public class
9.       
10. public synchronized  void
11. "synchronizedMethod1 called");  
12. try{  
13. 1000);  
14. catch(InterruptedException e){  
15.             e.printStackTrace();  
16.         }  
17. "synchronizedMethod1 done");  
18.     }  
19.       
20. public void
21. "method1 called");  
22. try{  
23. 1000);  
24. catch(InterruptedException e){  
25.             e.printStackTrace();  
26.         }  
27. "method1 done");  
28.     }  
29. }  
 
package com.bankht.UDP;
/** 
 * @author zhuyong 
 * 建立時間:2012-5-29 上午10:51:53 
 *
 * 類說明 :
 */
public class Common {
  
  public synchronized  void synchronizedMethod1(){
    System.out.println("synchronizedMethod1 called");
    try{
      Thread.sleep(1000);
    }catch(InterruptedException e){
      e.printStackTrace();
    }
    System.out.println("synchronizedMethod1 done");
  }
  
  public void method1(){
    System.out.println("method1 called");
    try{
      Thread.sleep(1000);
    }catch(InterruptedException e){
      e.printStackTrace();
    }
    System.out.println("method1 done");
  }
}      
[java] 
   ​​ view plain​​
   ​​copy​​
   ​​print​​
   ​​?​​
  
 
 
1. package
2. /** 
3.  * @author zhuyong 
4.  * 建立時間:2012-5-29 上午10:51:53 
5.  *
6.  * 類說明 :
7.  */
8. public class MyThread extends
9.       
10. private int id = 0;  
11. private
12.       
13. public MyThread(String name,int
14. super(name);  
15. this.common = common;  
16. this.id = id;  
17.     }  
18.       
19. public void
20. "Running Thread" + this.getName());  
21. try{  
22. if(this.id == 0){  
23.                 common.synchronizedMethod1();  
24. else{  
25.                 common.method1();  
26.             }  
27. catch(Exception e){  
28.             e.printStackTrace();  
29.         }  
30.     }  
31.       
32. public static void
33. new
34. new MyThread("MyThread-1", 0, c);  
35. new MyThread("MyThread-2", 1, c);  
36.         t1.start();  
37.         t2.start();  
38.     }  
39. }      
package com.bankht.UDP;
/** 
 * @author zhuyong 
 * 建立時間:2012-5-29 上午10:51:53 
 *
 * 類說明 :
 */
public class MyThread extends Thread{
  
  private int id = 0;
  private Common common;
  
  public MyThread(String name,int id,Common common ){
    super(name);
    this.common = common;
    this.id = id;
  }
  
  public void run(){
    System.out.println("Running Thread" + this.getName());
    try{
      if(this.id == 0){
        common.synchronizedMethod1();
      }else{
        common.method1();
      }
    }catch(Exception e){
      e.printStackTrace();
    }
  }
  
  public static void main(String[] args){
    Common c = new Common();
    MyThread t1 = new MyThread("MyThread-1", 0, c);
    MyThread t2 = new MyThread("MyThread-2", 1, c);
    t1.start();
    t2.start();
  }
}      

這裡是程式的輸出:

[html] 
   ​​ view plain​​
   ​​copy​​
   ​​print​​
   ​​?​​
  
 
 
1. Running ThreadMyThread-1  
2. Running ThreadMyThread-2  
3. synchronizedMethod1 called  
4. method1 called  
5. synchronizedMethod1 done  
6. method1 done      
Running ThreadMyThread-1
Running ThreadMyThread-2
synchronizedMethod1 called
method1 called
synchronizedMethod1 done
method1 done      

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

9.在一個對象上兩個線程可以調用兩個不同的同步執行個體方法嗎?

不能,因為一個對象已經同步了執行個體方法,線程擷取了對象的對象鎖。是以隻有執行完該方法釋放對象鎖後才能執行其它同步方法。看下面代碼示例非常清晰:Common 類有 synchronizedMethod1()和 synchronizedMethod2()方法,MyThread 調用這兩個方法。

[java] 
   ​​ view plain​​
   ​​copy​​
   ​​print​​
   ​​?​​
  
 
 
1. package
2. /** 
3.  * @author zhuyong 
4.  * 建立時間:2012-5-29 上午10:51:53 
5.  *
6.  * 類說明 :
7.  */
8. public class
9.       
10. public synchronized  void
11. "synchronizedMethod1 called");  
12. try{  
13. 1000);  
14. catch(InterruptedException e){  
15.             e.printStackTrace();  
16.         }  
17. "synchronizedMethod1 done");  
18.     }  
19. public synchronized  void
20. "synchronizedMethod2 called");  
21. try{  
22. 1000);  
23. catch(InterruptedException e){  
24.             e.printStackTrace();  
25.         }  
26. "synchronizedMethod2 done");  
27.     }  
28.       
29. }      
package com.bankht.UDP;
/** 
 * @author zhuyong 
 * 建立時間:2012-5-29 上午10:51:53 
 *
 * 類說明 :
 */
public class Common {
  
  public synchronized  void synchronizedMethod1(){
    System.out.println("synchronizedMethod1 called");
    try{
      Thread.sleep(1000);
    }catch(InterruptedException e){
      e.printStackTrace();
    }
    System.out.println("synchronizedMethod1 done");
  }
  public synchronized  void synchronizedMethod2(){
    System.out.println("synchronizedMethod2 called");
    try{
      Thread.sleep(1000);
    }catch(InterruptedException e){
      e.printStackTrace();
    }
    System.out.println("synchronizedMethod2 done");
  }
  
}      
[java] 
   ​​ view plain​​
   ​​copy​​
   ​​print​​
   ​​?​​
  
 
 
1. package
2. /** 
3.  * @author zhuyong 
4.  * 建立時間:2012-5-29 上午10:51:53 
5.  *
6.  * 類說明 :
7.  */
8. public class MyThread extends
9.       
10. private int id = 0;  
11. private
12.       
13. public MyThread(String name,int
14. super(name);  
15. this.common = common;  
16. this.id = id;  
17.     }  
18.       
19. public void
20. "Running Thread" + this.getName());  
21. try{  
22. if(this.id == 0){  
23.                 common.synchronizedMethod1();  
24. else{  
25.                 common.synchronizedMethod2();  
26.             }  
27. catch(Exception e){  
28.             e.printStackTrace();  
29.         }  
30.     }  
31.       
32. public static void
33. new
34. new MyThread("MyThread-1", 0, c);  
35. new MyThread("MyThread-2", 1, c);  
36.         t1.start();  
37.         t2.start();  
38.     }  
39. }      
package com.bankht.UDP;
/** 
 * @author zhuyong 
 * 建立時間:2012-5-29 上午10:51:53 
 *
 * 類說明 :
 */
public class MyThread extends Thread{
  
  private int id = 0;
  private Common common;
  
  public MyThread(String name,int id,Common common ){
    super(name);
    this.common = common;
    this.id = id;
  }
  
  public void run(){
    System.out.println("Running Thread" + this.getName());
    try{
      if(this.id == 0){
        common.synchronizedMethod1();
      }else{
        common.synchronizedMethod2();
      }
    }catch(Exception e){
      e.printStackTrace();
    }
  }
  
  public static void main(String[] args){
    Common c = new Common();
    MyThread t1 = new MyThread("MyThread-1", 0, c);
    MyThread t2 = new MyThread("MyThread-2", 1, c);
    t1.start();
    t2.start();
  }
}      

這裡是程式的輸出:

[html] 
   ​​ view plain​​
   ​​copy​​
   ​​print​​
   ​​?​​
  
 
 
1. Running ThreadMyThread-1  
2. Running ThreadMyThread-2  
3. synchronizedMethod1 called  
4. synchronizedMethod1 done  
5. synchronizedMethod2 called  
6. synchronizedMethod2 done      
Running ThreadMyThread-1
Running ThreadMyThread-2
synchronizedMethod1 called
synchronizedMethod1 done
synchronizedMethod2 called
synchronizedMethod2 done      

10.什麼是死鎖

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

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

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

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