天天看點

Java 線程池ExecutorService詳解

一、ExecutorService介紹

線程池:

    多線程技術主要解決處理器單元内多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。

    假設一個伺服器完成一項任務所需時間為:T1 建立線程時間,T2 線上程中執行任務的時間,T3 銷毀線程時間。

    如果:T1 + T3 遠大于 T2,則可以采用線程池,以提高伺服器性能。

                一個線程池包括以下四個基本組成部分:

                1、線程池管理器(ThreadPool):用于建立并管理線程池,包括 建立線程池,銷毀線程池,添加新任務;

                2、工作線程(PoolWorker):線程池中線程,在沒有任務時處于等待狀态,可以循環的執行任務;

                3、任務接口(Task):每個任務必須實作的接口,以供工作線程排程任務的執行,它主要規定了任務的入口,任務執行完後的收尾工作,任務的執行狀态等;

                4、任務隊列(taskQueue):用于存放沒有處理的任務。提供一種緩沖機制。

    線程池技術正是關注如何縮短或調整T1,T3時間的技術,進而提高伺服器程式性能的。它把T1,T3分别安排在伺服器程式的啟動和結束的時間段或者一些空閑的時間段,這樣在伺服器程式處理客戶請求時,不會有T1,T3的開銷了。

    線程池不僅調整T1,T3産生的時間段,而且它還顯著減少了建立線程的數目,看一個例子:

    假設一個伺服器一天要處理50000個請求,并且每個請求需要一個單獨的線程完成。線上程池中,線程數一般是固定的,是以産生線程總數不會超過線程池中線程的數目,而如果伺服器不利用線程池來處理這些請求則線程總數為50000。一般線程池大小是遠小于50000。是以利用線程池的伺服器程式不會為了建立50000而在處理請求時浪費時間,進而提高效率。

ExecutorService是Java中對線程池定義的一個接口,它java.util.concurrent包中,在這個接口中定義了和背景任務執行相關的方法:

Java API對ExecutorService接口的實作有兩個,是以這兩個即是Java線程池具體實作類:

1. ThreadPoolExecutor

2. ScheduledThreadPoolExecutor

除此之外,ExecutorService還繼承了Executor接口(注意區分Executor接口和Executors工廠類),這個接口隻有一個execute()方法,最後我們看一下整個繼承樹:

Java 線程池ExecutorService詳解

下方展示了一個線程的把任務委托異步執行的ExecutorService的示意圖。 

Java 線程池ExecutorService詳解

壹旦線程把任務委托給 ExecutorService,該線程就會繼續執行與運作任務無關的其它任務。

二、ExecutorService的建立

建立一個什麼樣的ExecutorService的執行個體(即線程池)需要g根據具體應用場景而定,不過Java給我們提供了一個Executors工廠類,它可以幫助我們很友善的建立各種類型ExecutorService線程池,Executors一共可以建立下面這四類線程池:

1. newCachedThreadPool 建立一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則建立線程。

2. newFixedThreadPool 建立一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。

3. newScheduledThreadPool 建立一個定長線程池,支援定時及周期性任務執行。

4. newSingleThreadExecutor 建立一個單線程化的線程池,它隻會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

備注:Executors隻是一個工廠類,它所有的方法傳回的都是ThreadPoolExecutor、ScheduledThreadPoolExecutor這兩個類的執行個體。

三、ExecutorService的使用

四、ExecutorService的執行

ExecutorService有如下幾個執行方法:

- execute(Runnable)

- submit(Runnable)

- submit(Callable)

- invokeAny(...)

- invokeAll(...)

4.1 execute(Runnable)

這個方法接收一個Runnable執行個體,并且異步的執行,請看下面的執行個體:

這個方法有個問題,就是沒有辦法獲知task的執行結果。如果我們想獲得task的執行結果,我們可以傳入一個Callable的執行個體(下面會介紹)。

4.2 submit(Runnable)

如果任務執行完成,future.get()方法會傳回一個null。注意,future.get()方法會産生阻塞。

4.3 submit(Callable)

submit(Callable)和submit(Runnable)類似,也會傳回一個Future對象,但是除此之外,submit(Callable)接收的是一個Callable的實作,Callable接口中的call()方法有一個傳回值,可以傳回任務的執行結果,而Runnable接口中的run()方法是void的,沒有傳回值。請看下面執行個體:

如果任務執行完成,future.get()方法會傳回Callable任務的執行結果。注意,future.get()方法會産生阻塞。

4.4 invokeAny(…)

invokeAny(...)方法接收的是一個Callable的集合,執行這個方法不會傳回Future,但是會傳回所有Callable任務中其中一個任務的執行結果。這個方法也無法保證傳回的是哪個任務的執行結果,反正是其中的某一個。請看下面執行個體:

大家可以嘗試執行上面代碼,每次執行都會傳回一個結果,并且傳回的結果是變化的,可能會傳回“Task2”也可是“Task1”或者其它。

4.5 invokeAll(…)

invokeAll(...)與 invokeAny(...)類似也是接收一個Callable集合,但是前者執行之後會傳回一個Future的List,其中對應着每個Callable任務執行後的Future對象。情況下面這個執行個體:

五、ExecutorService的關閉

當我們使用完成ExecutorService之後應該關閉它,否則它裡面的線程會一直處于運作狀态。

舉個例子,如果的應用程式是通過main()方法啟動的,在這個main()退出之後,如果應用程式中的ExecutorService沒有關閉,這個應用将一直運作。之是以會出現這種情況,是因為ExecutorService中運作的線程會阻止JVM關閉。

如果要關閉ExecutorService中執行的線程,我們可以調用ExecutorService.shutdown()方法。在調用shutdown()方法之後,ExecutorService不會立即關閉,但是它不再接收新的任務,直到目前所有線程執行完成才會關閉,所有在shutdown()執行之前送出的任務都會被執行。

如果我們想立即關閉ExecutorService,我們可以調用ExecutorService.shutdownNow()方法。這個動作将跳過所有正在執行的任務和被送出還沒有執行的任務。但是它并不對正在執行的任務做任何保證,有可能它們都會停止,也有可能執行完成。

繼續閱讀