線程池的工作原理及使用示例
. 為什麽要使用線程池?
我們現在考慮最簡單的服務器工作模型:服務器每當接收到一個客戶端請求時就創建一個線程為其服務。這種模式理論上可以工作的很好,但實際上會存在一些缺陷,服務器應用程序中經常出現的情況是單個客戶端請求處理的任務很簡單但客戶端的數目卻是巨大的,因此服務器在創建和銷毀線程所花費的時間和系統資源可能比處理客戶端請求處理的任務花費的時間和資源更多。
線程池技術就是為了解決上述問題而出現的。合理的使用線程池便可重復利用已創建的線程,以減少在創建線程和銷毀線程上花費的時間和資源。除此之外,線程池在某些情況下還能動態的調整工作線程的數量,以平衡資源消耗和工作效率。同時線程池還提供了對池中工作線程進行統一的管理的相關方法。
2. 線程池的簡要工作模型
線程池的工作模型主要兩部分組成,一部分是運行Runnable的Thread對象,另一部分就是阻塞隊列。
由線程池創建的Thread對象其內部的run方法會通過阻塞隊列的take方法獲取一個Runnable對象,然後執行這個Runnable對象的run方法(即,在Thread的run方法中調用Runnable對象的run方法)。當Runnable對象的run方法執行完畢以後,Thread中的run方法又循環的從阻塞隊列中獲取下一個Runnable對象繼續執行。這樣就實現了Thread對象的重復利用,也就減少了創建線程和銷毀線程所消耗的資源。
當需要向線程池提交任務時會調用阻塞隊列的offer方法向隊列的尾部添加任務。提交的任務實際上就是是Runnable對象或Callable對象。
上述僅僅是最簡略的線程池工作模型,但體現了線程池的核心思想,而至於線程池中線程的動態的創建和自行銷毀、動態調整實際工作的線程數、阻塞隊列的排隊策略以及隊列的長度等等細節問題會在本博客中“線程池 ThreadPoolExecutor、Executors源代碼分析”的文章中詳細介紹。
3. Executor、ExecutorService、AbstractExecutorService、ThreadPoolExecutor、Executors之間的關系
可以看出ExecutorService接口定義了線程池應該具有的行為特征(圖中沒有列出了ExecutorsService的全部方法),而真正實現線程池的是ThreadPoolExecutor類。由於ThreadPoolExecutor的構造函數參數眾多且某些參數又會對線程的工作有著重大的影響。為了防止使用者錯誤搭配ThreadPoolExecutor構造函數的各個參數以及更加方便簡潔的創建ThreadPoolExecutor對象,JavaSE中又定義了Executors類,Eexcutors類提供了創建常用配置線程池的方法。
4. ExecutorService接口介紹
ExecutorService接口定義了線程池應該具有的行為特征,它具有以下主要方法
1 2 3 4 5 6 7 8 9 10 11 12 void
shutdown();
List<Runnable> shutdownNow();
boolean
isTerminated();
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<?
extends
Callable<T>> tasks)
throws
InterruptedException;
<T> T invokeAny(Collection<?
extends
Callable<T>> tasks)
throws
InterruptedException, ExecutionException;
submit相關方法:向線程池添加執行的任務
shutdown方法:此方法執行後不得向線程池再提交任務,如果有空閑線程則銷毀空閑線程,等待所有正在執行的任務及位於阻塞隊列中的任務執行結束,然後銷毀所有線程。
shutdownNow方法:此方法執行後不得向線程池再提交任務,如果有空閑線程則銷毀空閑線程,取消所有位於阻塞隊列中的任務,並將其放入List<Runnable>容器,作為返回值。取消正在執行的線程(實際上僅僅是設置正在執行線程的中斷標誌位)。
invokeAll方法:一次性向線程池提交多個任務,並返回全部結果。
invokeAny方法:一次性向線程池提交多個任務,並將第一個得到的結果作為返回值,然後立刻取消所有正在執行的線程。
isTerminated方法:池中的線程全部銷毀後,該方法返回真,否則返回假。
由於ExecutorService繼承了Executor接口,所以它其實還具有一個execute方法,它的功能是向線程池提交一個Runnable對象的任務。submit方法中就調用了execute方法,不過submit在調用execute方法之前將Runnable對象或Callable對象包裝成了RunnableFuture對象,然後交由execute來提交任務。
5. 線程池使用示例
package javalearning; import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolDemo { static class Task implements Runnable{ private String id; Task(String id){ this.id = id; } @Override public void run() { System.out.println("Thread "+id+" is working"); try { //每個任務隨機延時1s以內的時間以模擬線程的運行 Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread "+id+" over"); } } public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(3);//線程池中,3工作線程 threadPool.execute(new Task("a")); threadPool.execute(new Task("b")); threadPool.execute(new Task("c")); threadPool.execute(new Task("d")); threadPool.execute(new Task("e")); threadPool.shutdown(); while(!threadPool.isTerminated()){ } System.out.println("Thread Pool is over"); } }
從執行結果可以看出,同時最多有三個線程並發執行,超過三個任務以後,線程池會將任務存儲於阻塞隊列中。
Thread a is working Thread c is working Thread b is working Thread a over Thread d is working Thread d over Thread e is working Thread e over Thread b over Thread c over Thread Pool is over
線程池的工作原理及使用示例