1. 程式人生 > >關於多執行緒的最常見的面試題總結

關於多執行緒的最常見的面試題總結

  • 如果不會這幾道多執行緒基礎題,請自覺面壁!
  1. 簡述執行緒,程式、程序的基本概念。以及他們之間關係是什麼?

執行緒與程序相似,但執行緒是一個比程序更小的執行單位。一個程序在其執行的過程中可以產生多個執行緒與程序不同的是同類的多個執行緒共享同一塊記憶體空間和一組系統資源,所以系統在產生一個執行緒,或是在各個執行緒之間作切換工作時,負擔要比程序小得多,也正因為如此,執行緒也被稱為輕量級程序。

程式是含有指令和資料的檔案,被儲存在磁碟或其他的資料儲存裝置中,也就是說程式是靜態的程式碼。

程序是程式的一次執行過程,是系統執行程式的基本單位,因此程序是動態的。系統執行一個程式即是一個程序從建立,執行到消亡的過程。簡單來說,一個程序就是一個執行中的程式,它在計算機中一個指令接著一個指令地執行著,同時,每個程序還佔有某些系統資源如CPU時間,記憶體空間,檔案,輸入輸出裝置的使用權等等。換句話說,當程式在執行時,將會被作業系統載入記憶體中

執行緒是程序劃分成的更小的執行單位。執行緒和程序最大的不同在於基本上各程序是獨立的,而各執行緒則不一定,因為同一程序中的執行緒極有可能會相互影響。從另一角度來說,程序屬於作業系統的範疇,主要是同一段時間內,可以同時執行一個以上的程式,而執行緒則是在同一程式內幾乎同時執行一個以上的程式段。

2.建立執行緒的三種方式

(1)繼承Thread類

public class MyThread extends Thread {

    @Override

    public void run() {

        super.run();

        System.out.println("MyThread");

    }

}

public class Run {

    public static void main(String[] args) {

        MyThread mythread = new MyThread();

        mythread.start();

        System.out.println("執行結束");

    }

}

從上面的執行結果可以看出:執行緒是一個子任務,CPU以不確定的方式,或者說是以隨機的時間來呼叫執行緒中的run方法。

(2)實現Runnable介面

推薦實現Runnable介面方式開發多執行緒,因為Java單繼承但是可以實現多個介面。

<myrunnable.java< p="" style="box-sizing: border-box;"></myrunnable.java>>

public class MyRunnable implements Runnable {

    @Override

    public void run() {

        System.out.println("MyRunnable");

    }

}

Run.java

public class Run {

    public static void main(String[] args) {

        Runnable runnable=new MyRunnable();

        Thread thread=new Thread(runnable);

        thread.start();

        System.out.println("執行結束!");

    }

}

(3)使用執行緒池

在《阿里巴巴Java開發手冊》“併發處理”這一章節,明確指出執行緒資源必須通過執行緒池提供,不允許在應用中自行顯示建立執行緒。

為什麼呢?

使用執行緒池的好處是減少在建立和銷燬執行緒上所消耗的時間以及系統資源開銷,解決資源不足的問題。如果不使用執行緒池,有可能會造成系統建立大量同類執行緒而導致消耗完記憶體或者“過度切換”的問題。

另外《阿里巴巴Java開發手冊》中強制執行緒池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓開發者更加明確執行緒池的執行規則,規避資源耗盡的風險

Executors 返回執行緒池物件的弊端如下:

FixedThreadPool 和 SingleThreadExecutor : 允許請求的佇列長度為 Integer.MAX_VALUE,可能堆積大量的請求,從而導致OOM。

CachedThreadPool 和 ScheduledThreadPool : 允許建立的執行緒數量為 Integer.MAX_VALUE ,可能會建立大量執行緒,從而導致OOM。

3.執行緒有哪些基本狀態?這些狀態是如何定義的?

新建(new):新建立了一個執行緒物件

就緒(runnable):執行緒物件建立後,其他執行緒(比如main執行緒)呼叫了該物件的start()方法。該狀態的執行緒位於可執行執行緒池中,等待被執行緒排程選中,獲 取cpu的使用權。

執行(running):可執行狀態(runnable)的執行緒獲得了cpu時間片(timeslice),執行程式程式碼

阻塞(block):阻塞狀態是指執行緒因為某種原因放棄了cpu使用權,也即讓出了cpu timeslice,暫時停止執行。直到執行緒進入可執行(runnable)狀態,才有 機會再次獲得cpu timeslice轉到執行(running)狀態。阻塞的情況分三種:

(1). 等待阻塞:執行(running)的執行緒執行o.wait()方法,JVM會把該執行緒放 入等待佇列 (waitting queue)中。

(2). 同步阻塞:執行(running)的執行緒在獲取物件的同步鎖時,若該同步鎖被別的執行緒 佔用,則JVM會把該執行緒放入鎖池(lock pool)中。

(3). 其他阻塞: 執行(running)的執行緒執行Thread.sleep(long ms)或t.join()方法,或者發出 了I/O請求時,JVM會把該執行緒置為阻塞狀態。當sleep()狀態超時join()等待執行緒終止 或者超時、或者I/O處理完畢時,執行緒重新轉入可執行(runnable)狀態。

死亡(dead):執行緒run()、main()方法執行結束,或者因異常退出了run()方法,則該執行緒結束生命週期。死亡的執行緒不可再次復生。

備註: 可以用早起坐地鐵來比喻這個過程:

還沒起床:sleeping

起床收拾好了,隨時可以坐地鐵出發:Runnable

等地鐵來:Waiting

地鐵來了,但要排隊上地鐵:I/O阻塞

上了地鐵,發現暫時沒座位:synchronized阻塞

地鐵上找到座位:Running

到達目的地:Dead

4.sleep()方法和wait()方法簡單對比

 5.何為多執行緒?

  • 兩者最主要的區別在於:sleep方法沒有釋放鎖,而wait方法釋放了鎖 。
  • 兩者都可以暫停執行緒的執行。
  • Wait通常被用於執行緒間互動/通訊,sleep通常被用於暫停執行。
  • wait()方法被呼叫後,執行緒不會自動甦醒,需要別的執行緒呼叫同一個物件上的notify()或者notifyAll()方法。sleep()方法執行完成後,執行緒會自動甦醒。  

多執行緒就是多個執行緒同時執行或交替執行。單核CPU的話是順序執行,也就是交替執行。多核CPU的話,因為每個CPU有自己的運算器,所以在多個CPU中可以同時執行。

6.為什麼多執行緒是必要的?

使用執行緒可以把佔據長時間的程式中的任務放到後臺去處理。

使用者介面可以更加吸引人,這樣比如使用者點選了一個按鈕去觸發某些事件的處理,可以彈出一個進度條來顯示處理的進度。

程式的執行速度可能加快。

7.執行緒的優先順序

每個執行緒都具有各自的優先順序,執行緒的優先順序可以在程式中表明該執行緒的重要性,如果有很多執行緒處於就緒狀態,系統會根據優先順序來決定首先使哪個執行緒進入執行狀態。但這個並不意味著低 優先順序的執行緒得不到執行,而只是它執行的機率比較小,如垃圾回收機制執行緒的優先順序就比較低。所以很多垃圾得不到及時的回收處理。

執行緒優先順序具有繼承特性。 比如A執行緒啟動B執行緒,則B執行緒的優先順序和A是一樣的。

執行緒優先順序具有隨機性。 也就是說執行緒優先順序高的不一定每一次都先執行完。

Thread類中包含的成員變數代表了執行緒的某些優先順序。如Thread.MINPRIORITY(常數1),Thread.NORMPRIORITY(常數5),Thread.MAXPRIORITY(常數10)。其中每個執行緒的優先順序都在Thread.MINPRIORITY(常數1) 到Thread.MAXPRIORITY(常數10) 之間,在預設情況下優先順序都是Thread.NORMPRIORITY(常數5)。

學過作業系統這門課程的話,我們可以發現多執行緒優先順序或多或少借鑑了作業系統對程序的管理。

8.Java多執行緒分類

使用者執行緒

執行在前臺,執行具體的任務,如程式的主執行緒、連線網路的子執行緒等都是使用者執行緒

守護執行緒

執行在後臺,為其他前臺執行緒服務.也可以說守護執行緒是JVM中非守護執行緒的 “傭人”。

特點: 一旦所有使用者執行緒都結束執行,守護執行緒會隨JVM一起結束工作

應用: 資料庫連線池中的檢測執行緒,JVM虛擬機器啟動後的檢測執行緒

最常見的守護執行緒: 垃圾回收執行緒

如何設定守護執行緒?

可以通過呼叫 Thead 類的 setDaemon(true) 方法設定當前的執行緒為守護執行緒。

注意事項:

1.  setDaemon(true)必須在start()方法前執行,否則會丟擲IllegalThreadStateException異常

2. 在守護執行緒中產生的新執行緒也是守護執行緒

3. 不是所有的任務都可以分配給守護執行緒來執行,比如讀寫操作或者計算邏輯

9.我們呼叫start()方法時會執行run()方法,為什麼我們不能直接呼叫run()方法?

這是另一個非常經典的java多執行緒面試問題,而且在面試中會經常被問到。很簡單,但是很多人都會答不上來!

new一個Thread,執行緒進入了新建狀態;呼叫start()方法,會啟動一個執行緒並使執行緒進入了就緒狀態,當分配到時間片後就可以開始運行了。 start()會執行執行緒的相應準備工作,然後自動執行run()方法的內容,這是真正的多執行緒工作。 而直接執行run()方法,會把run方法當成一個mian執行緒下的普通方法去執行,並不會在某個執行緒中執行它,所以這並不是多執行緒工作。

總結: 呼叫start方法方可啟動執行緒並使執行緒進入就緒狀態,而run方法只是thread的一個普通方法呼叫,還是在主執行緒裡執行。