java核心技術卷I-執行器

執行器

構建一個新的線程是有一定代價的, 因爲涉及與操作系統的交互。如果程序中創建了大量的生命期很短的線程,應該使用線程池( thread pool)。一個線程池中包含許多準備運行的空閒線程。將 Runnable 對象交給線程池, 就會有一個線程調用 run 方法。 當 run 方法退出時,線程不會死亡,而是在池中準備爲下一個請求提供服務。
另一個使用線程池的理由是減少併發線程的數目。創建大量線程會大大降低性能甚至使虛擬機崩潰。如果有一個會創建許多線程的算法, 應該使用一個線程數“ 固定的” 線程池以限制併發線程的總數。
執行器( Executor) 類有許多靜態工廠方法用來構建線程池
在這裏插入圖片描述

線程池

newCachedThreadPool方法構建了一個線程池, 對於每個任務, 如果有空閒線程可用,立即讓它執行任務,如果沒有可用的空閒線程, 則創建一個新線程。
newFixedThreadPool 方法構建一個具有固定大小的線程池。 如果提交的任務數多於空閒的線程數, 那麼把得不到服務的任務放置到隊列中。當其他任務完成以後再運行它們。
newSingleThreadExecutor 是一個退化了的大小爲 1 的線程池: 由一個線程執行提交的任務,一個接着一個。這 3 個方法返回實現了
ExecutorService 接口的 ThreadPoolExecutor 類的對象。
可用下面的方法之一將一個 Runnable 對象或 Callable 對象提交給 ExecutorService:

Future<?> submit(Runnable task)
Future<T> submit(Runnable task, T result)
Future<T> submit(Callable<T> task)

該池會在方便的時候儘早執行提交的任務。調用 submit 時,會得到一個 Future 對象, 可用來查詢該任務的狀態。
第一個 submit 方法返回一個奇怪樣子的 Future<?>。可以使用這樣一個對象來調用isDone、 cancel 或 isCancelled。但是, get 方法在完成的時候只是簡單地返回 null。
第二個版本的 Submit 也提交一個 Runnable, 並且 Future 的 get 方法在完成的時候返回指定的 result 對象。
第三個版本的 Submit 提交一個 Callable, 並且返回的 Future 對象將在計算結果準備好的時候得到它。
當用完一個線程池的時候, 調用 shutdown。該方法啓動該池的關閉序列。被關閉的執行器不再接受新的任務。當所有任務都完成以後,線程池中的線程死亡。另一種方法是調用shutdownNow。該池取消尚未開始的所有任務並試圖中斷正在運行的線程。
下面總結了在使用連接池時應該做的事:

1 ) 調用 Executors 類中靜態的方法 newCachedThreadPool 或 newFixedThreadPool。
2 ) 調用 submit 提交 Runnable 或 Callable 對象。
3 ) 如果想要取消一個任務, 或如果提交 Callable對象, 那就要保存好返回的 Future 對象。
4 ) 當不再提交任何任務時,調用 shutdown。

預定執行

ScheduledExecutorService 接口具有爲預定執行( Scheduled Execution) 或 重 復 執 行 任務而設計的方法。它是一種允許使用線程池機制的 java.util.Timer 的泛化。Executors 類的newScheduledThreadPool 和 newSingleThreadScheduledExecutor 方法將返回實現了 ScheduledExecutorService 接口的對象。
可以預定 Runnable 或 Callable 在初始的延遲之後只運行一次。也可以預定一個Runnable對象週期性地運行。

控制任務組

你已經瞭解瞭如何將一個執行器服務作爲線程池使用, 以提高執行任務的效率。 有時, 使用執行器有更有實際意義的原因, 控制一組相關任務。例如, 可以在執行器中使用shutdownNow 方法取消所有的任務。
invokeAny 方法提交所有對象到一個 Callable 對象的集合中,並返回某個已經完成了的任務的結果。無法知道返回的究竟是哪個任務的結果, 也許是最先完成的那個任務的結果。對於搜索問題, 如果你願意接受任何一種解決方案的話,你就可以使用這個方法。例如, 假定你需要對一個大整數進行因數分解計算來解碼 RSA 密碼。可以提交很多任務, 每一個任務使用不同範圍內的數來進行分解。只要其中一個任務得到了答案,計算就可以停止了。
invokeAll 方法提交所有對象到一個 Callable 對象的集合中,並返回一個 Future 對象的列表,代表所有任務的解決方案。當計算結果可獲得時, 可以像下面這樣對結果進行處理:

List<Callable<T>> tasks = . . .;
List<Future<T>> results = executor.invokeAll(tasks):
for (Future<T> result : results)
	processFurther(result.get());

這個方法的缺點是如果第一個任務恰巧花去了很多時間,則可能不得不進行等待。將結
果按可獲得的順序保存起來更有實際意義。可以用 ExecutorCompletionService 來進行排列。
用常規的方法獲得一個執行器。然後, 構建一個 ExecutorCompletionService, 提交任務給完成服務( completion service。) 該服務管理 Future 對象的阻塞隊列,其中包含已經提交的任務的執行結果(當這些結果成爲可用時。) 這樣一來,相比前面的計算, 一個更有效的組織形式如下:

ExecutorCompletionService<T> service = new ExecutorCompletionService<>(executor):
for (Callable<T> task : tasks) service,submit(task);
for (int i = 0; i < tasks.size();i ++)
	processFurther(service.take().get());
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章