不少核心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 類在獨立的線程中調用這兩個方法。
- 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 而言,線程死鎖可能發生在一下狀況。
11.什麼是線程餓死,什麼是活鎖?
線程餓死和活鎖雖然不想是死鎖同樣的常見問題,可是對於併發編程的設計者來講就像一次邂逅同樣。
當全部線程阻塞,或者因爲須要的資源無效而不能處理,不存在非阻塞線程使資源可用。JavaAPI 中線程活鎖可能發生在如下情形:
這裏的問題並不詳盡,我相信還有不少重要的問題並未說起,您認爲還有哪些問題應該包括在上面呢?歡迎在評論中分享任何形式的問題與建議。