1. 程式人生 > >Java之多執行緒面試題

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 難於理解,下面這些引用連線有助於你更好的理解它。

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 類在獨立的執行緒中呼叫這兩個方法。

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 ("Method 1 called");  
                try {  
                        Thread.sleep (1000);  
                 } catch (InterruptedException e) {  
                        e.printStackTrace ();  
                 }  
                System.out.println ("Method 1 done");  } 
        } 
       public class MyThread extends Thread {  
                private int id = 0;  
                private Common common;     
                public MyThread (String name, int no, Common object) {  
                         super(name);  
                         common = object;  
                         id = no;  
          }      
         public void run () {  
                    System.out.println ("Running Thread" + this.getName ());  
                    try {  
                               if (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 ();  
         }  
}  
這裡是程式的輸出:
Running ThreadMyThread-1  
synchronizedMethod1 called  
Running ThreadMyThread-2  
Method 1 called  
synchronizedMethod1 done  
Method 1 done 

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

9.在一個物件上兩個執行緒可以呼叫兩個不同的同步例項方法嗎?

不能,因為一個物件已經同步了例項方法,執行緒獲取了物件的物件鎖。所以只有執行完該方法釋放物件鎖後才能執行其它同步方法。看下面程式碼示例非常清晰:Common 類有 synchronizedMethod1()和 synchronizedMethod2()方法,MyThread 呼叫這兩個方法。

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");  
           }  
} 
public class MyThread extends Thread { 
          private int id = 0;  
          private Common common;     
          public MyThread (String name, int no, Common object) {  
                   super(name);  
                   common = object;  
                   id = no;  
          }     
          public void run () {  
                   System.out.println ("Running Thread" + this.getName ());  
                   try {  
                            if (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 ();  
           }  
} 

10.什麼是死鎖

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

  • 當兩個執行緒相互呼叫 Thread.join ()
  • 當兩個執行緒使用巢狀的同步塊,一個執行緒佔用了另外一個執行緒必需的鎖,互相等待時被阻塞就有可能出現死鎖。

11.什麼是執行緒餓死,什麼是活鎖?

執行緒餓死和活鎖雖然不想是死鎖一樣的常見問題,但是對於併發程式設計的設計者來說就像一次邂逅一樣。

當所有執行緒阻塞,或者由於需要的資源無效而不能處理,不存在非阻塞執行緒使資源可用。JavaAPI 中執行緒活鎖可能發生在以下情形:

  • 當所有執行緒在程式中執行 Object.wait (0),引數為 0 的 wait 方法。程式將發生活鎖直到在相應的物件上有執行緒呼叫 Object.notify ()或者 Object.notifyAll ()。
  • 當所有執行緒卡在無限迴圈中。