摘要:本文只介紹中線程池的基本使用,不會過多的涉及到線程池的原理??删彺婢€程的線程池創建一個可緩存線程的線程池。首先是從接口繼承到的方法使用該方法即將一個任務交給線程池去執行。方法方法的作用是向線程池發送關閉的指令。
首先,我們為什么需要線程池?
讓我們先來了解下什么是 對象池 技術。某些對象(比如線程,數據庫連接等),它們創建的代價是非常大的 —— 相比于一般對象,它們創建消耗的時間和內存都很大(而且這些對象銷毀的代價比一般對象也大)。所以,如果我們維護一個 池,每次使用完這些對象之后,并不銷毀它,而是將其放入池中,下次需要使用時就直接從池中取出,便可以避免這些對象的重復創建;同時,我們可以固定 池的大小,比如設置池的大小為 N —— 即池中只保留 N 個這類對象 —— 當池中的 N 個對象都在使用中的時候,為超出數量的請求設置一種策略,比如 排隊等候 或者 直接拒絕請求 等,從而避免頻繁的創建此類對象。
線程池 即對象池的一種(池中的對象為線程 Thread),類似的還有 數據庫連接池(池中對象為數據庫連接 Connection)。合理利用線程池能夠帶來三個好處(參考文末的 References[1]):
降低資源消耗,通過重復利用已創建的線程,降低線程創建和銷毀時造成的時間和內存上的消耗;
提升響應速度,當任務到達時,直接使用線程池中的線程來運行任務,使得任務可以不需要等到線程創建就能立即執行;
提高線程的可管理性,線程是開銷很大的對象,如果無限制的創建線程,不僅會快速消耗系統資源,還會降低系統的穩定性;而使用線程池可以對線程進行統一的分配和調控。
本文只介紹 Java 中線程池的基本使用,不會過多的涉及到線程池的原理。如果有興趣的讀者需要深入理解線程池的實現原理,可以參考文末的 References。
JDK 中線程池的基礎架構如下:
執行器 Executor 是頂級接口,只包含了一個 execute 方法,用來執行一個 Runnable 任務:
執行器服務 ExecutorService 接口繼承了 Executor 接口,ExecutorService 是所有線程池的基礎接口,它定義了 JDK 中線程池應該實現的基本方法:
線程池執行器 ThreadPoolExecutor 是基礎線程池的核心實現,并且可以通過定制 ThreadPoolExecutor 的構造參數或者繼承 ThreadPoolExecutor,實現自己的線程池;
ScheduledThreadPoolExecutor 繼承自 ThreadPoolExecutor,是能執行周期性任務或定時任務的線程池;
ForkJoinPool 是 JDK1.7 時添加的類,作為對 Fork/Join 型線程池的實現。
本文只介紹 ThreadPoolExecutor 線程池的使用,ScheduledThreadPoolExecutor 和 ForkJoinPool 會在之后的文章中介紹。
查看 ThreadPoolExecutor 的源碼可知,在 ThreadPoolExecutor 的內部,將每個池中的線程包裝為了一個 Worker:
然后在 ThreadPoolExecutor 中定義了一個 HashSet
設置一個合適的線程池(即自定義 ThreadPoolExecutor)是比較麻煩的,因此 JDK 通過 Executors 這個工廠類為我們提供了一些預先定義好的線程池:
1、固定大小的線程池
創建一個包含 nThreads 個工作線程的線程池,這 nThreads 個線程共享一個無界隊列(即不限制大小的隊列);當新任務提交到線程池時,如果當前沒有空閑線程,那么任務將放入隊列中進行等待,直到有空閑的線程來從隊列中取出該任務并運行。
(通過 Runtime.getRuntime().availableProcessors() 可以獲得當前機器可用的處理器個數,對于計算密集型的任務,固定大小的線程池的 nThreads 設置為這個值時,一般能獲得最大的 CPU 使用率)
2、單線程線程池
創建一個只包含一個工作線程的線程池,它的功能可以簡單的理解為 即 newFixedThreadPool 方法傳入參數為 1 的情況。但是與 newFixedThreadPool(1) 不同的是,如果線程池中這個唯一的線程意外終止,線程池會創建一個新線程繼續執行之后的任務。
3、可緩存線程的線程池
創建一個可緩存線程的線程池。當新任務提交到線程池時,如果當前線程池中有空閑線程可用,則使用空閑線程來運行任務,否則新建一個線程來運行該任務,并將該線程添加到線程池中;而且該線程池會終止并移除那些超過 60 秒未被使用的空閑線程。所以這個線程池表現得就像緩存,緩存的資源為線程,緩存的超時時間為 60 秒。根據 JDK 的文檔,當任務的運行時間都較短的時候,該線程池有利于提高性能。
我們看到每個構造線程池的工廠方法都有一個帶 ThreadFactory 的重載形式。ThreadFactory 即線程池用來新建線程的工廠,每次線程池需要新建一個線程時,調用的就是這個 ThreadFactory 的 newThread 方法:
(如果不提供自定義的 ThreadFactory,那么使用的就是 DefaultThreadFactory —— Executors 內定義的內部類)
比如我們要為線程池中的每個線程提供一個特定的名字,那么我們就可以自定義 ThreadFactory 并重寫其 newThread 方法:
public class SimpleThreadFactory implements ThreadFactory { private AtomicInteger id = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("Test_Thread-" + id.getAndIncrement()); return thread; } }
通過 JDK 的源碼我們可以知道,以上三種線程池的實現都是基于 ThreadPoolExecutor:
下面我們來看一下線程池的基礎接口 ExecutorService 中每個方法的含義。
首先是從 Executor 接口繼承到的 execute 方法:
使用該方法即將一個 Runnable 任務交給線程池去執行。
submit 方法:
submit 方法會提交一個任務去給線程池執行,該任務可以是帶返回結果的 Callable
向線程池提交一個 Runnable 或者 Callable
將 任務 作為參數使用 newTaskFor 方法構造出 FutureTask
(由 上一篇文章 可知,FutureTask
![newTaskFor 方法][19]
線程池使用 execute 方法將 FutureTask
然后 Worker 執行任務(即運行 run 方法),在任務完成后,為 Future
通過 Future
invokeAll 方法:
invokeAll 方法可以一次執行多個任務,但它并不同等于多次調用 submit 方法。submit 方法是非阻塞的,每次調用 submit 方法提交任務到線程池之后,會立即返回與任務相關聯的 Future
而 invokeAll 方法是阻塞的,只有當提交的多個任務都執行完畢之后,invokeAll 方法才會返回,執行結果會以List
invokeAny 方法:
invokeAny 方法也是阻塞的,與 invokeAll 方法的不同之處在于,當所提交的一組任務中的任何一個任務完成之后,invokeAny 方法便會返回(返回的結果便是那個已經完成的任務的返回值),而其他任務會被取消(中斷)。
舉一個 invokeAny 使用的例子:電腦有 C、D、E、F 四個盤,我們需要找一個文件,但是我們不知道這個文件位于哪個盤中,我們便可以使用 invokeAny,并提交四個任務(對應于四個線程)分別查找 C、D、E、F 四個盤,如果哪個線程找到了這個文件,那么此時 invokeAny 便停止阻塞并返回結果,同時取消其他任務。
shutdown 方法:
shutdown 方法的作用是向線程池發送關閉的指令。一旦在線程池上調用 shutdown 方法之后,線程池便不能再接受新的任務;如果此時還向線程池提交任務,那么將會拋出 RejectedExecutionException 異常。之后線程池不會立刻關閉,直到之前已經提交到線程池中的所有任務(包括正在運行的任務和在隊列中等待的任務)都已經處理完成,才會關閉。
shutdownNow 方法:
與 shutdown 不同,shutdownNow 會立即關閉線程池 —— 當前在線程池中運行的任務會全部被取消,然后返回線程池中所有正在等待的任務。
(值得注意的是,我們 必須顯式的關閉線程池,否則線程池不會自己關閉)
awaitTermination 方法:
awaitTermination 可以用來判斷線程池是否已經關閉。調用 awaitTermination 之后,在 timeout 時間內,如果線程池沒有關閉,則阻塞當前線程,否則返回 true;當超過 timeout 的時間后,若線程池已經關閉則返回 true,否則返回 false。該方法一般這樣使用:
任務全部提交完畢之后,我們調用 shutdown 方法向線程池發送關閉的指令;
然后我們通過 awaitTermination 來檢測到線程池是否已經關閉,可以得知線程池中所有的任務是否已經執行完畢;
線程池執行完已經提交的所有任務,并將自己關閉;
調用 awaitTermination 方法的線程停止阻塞,并返回 true;
isShutdown() 方法,如果線程池已經調用 shutdown 或者 shutdownNow,則返回 true,否則返回 false;
isTerminated() 方法,如果線程池已經調用 shutdown 并且線程池中所有的任務已經執行完畢,或者線程池調用了 shutdownNow,則返回 true,否則返回 false。
通過以上介紹,我們已經了解了 ExecutorService 中所有方法的功能,現在讓我們來實踐 ExecutorService 的功能。
我們繼續使用 上一篇文章 的兩個例子中的任務,首先是任務類型為 Runnable 的情況:
import java.util.*; import java.util.concurrent.*; public class RunnableTest { public static void main(String[] args) throws Exception { System.out.println("使用線程池運行 Runnable 任務:"); ExecutorService threadPool = Executors.newFixedThreadPool(5); // 創建大小固定為 5 的線程池 Listtasks = new ArrayList<>(10); for (int i = 0; i < 10; i++) { AccumRunnable task = new AccumRunnable(i * 10 + 1, (i + 1) * 10); tasks.add(task); threadPool.execute(task); // 讓線程池執行任務 task } threadPool.shutdown(); // 向線程池發送關閉的指令,等到已經提交的任務都執行完畢之后,線程池會關閉 threadPool.awaitTermination(1, TimeUnit.HOURS); // 等待線程池關閉,等待的最大時間為 1 小時 int total = 0; for (AccumRunnable task : tasks) { total += task.getResult(); // 調用在 AccumRunnable 定義的 getResult 方法獲得返回的結果 } System.out.println("Total: " + total); } static final class AccumRunnable implements Runnable { private final int begin; private final int end; private int result; public AccumRunnable(int begin, int end) { this.begin = begin; this.end = end; } @Override public void run() { result = 0; try { for (int i = begin; i <= end; i++) { result += i; Thread.sleep(100); } } catch (InterruptedException ex) { ex.printStackTrace(System.err); } System.out.printf("(%s) - 運行結束,結果為 %d ", Thread.currentThread().getName(), result); } public int getResult() { return result; } } }
運行結果:
可以看到 NetBeans 給出的運行時間為 2 秒 —— 因為每個任務需要 1 秒的時間,而線程池中的線程個數固定為 5 個,所以線程池最多同時處理 5 個任務,因而 10 個任務總共需要 2 秒的運行時間。
任務類型為 Callable:
import java.util.*; import java.util.concurrent.*; public class CallableTest { public static void main(String[] args) throws Exception { System.out.println("使用線程池運行 Callable 任務:"); ExecutorService threadPool = Executors.newFixedThreadPool(5); // 創建大小固定為 5 的線程池 List> futures = new ArrayList<>(10); for (int i = 0; i < 10; i++) { AccumCallable task = new AccumCallable(i * 10 + 1, (i + 1) * 10); Future future = threadPool.submit(task); // 提交任務 futures.add(future); } threadPool.shutdown(); // 向線程池發送關閉的指令,等到已經提交的任務都執行完畢之后,線程池會關閉 int total = 0; for (Future future : futures) { total += future.get(); // 阻塞,直到任務結束,返回任務的結果 } System.out.println("Total: " + total); } static final class AccumCallable implements Callable { private final int begin; private final int end; public AccumCallable(int begin, int end) { this.begin = begin; this.end = end; } @Override public Integer call() throws Exception { int result = 0; for (int i = begin; i <= end; i++) { result += i; Thread.sleep(100); } System.out.printf("(%s) - 運行結束,結果為 %d ", Thread.currentThread().getName(), result); return result; } } }
運行結果:
改寫上面的代碼,使用 invokeAll 來直接執行一組任務:
public static void main(String[] args) throws Exception { System.out.println("使用線程池 invokeAll 運行一組 Callable 任務:"); ExecutorService threadPool = Executors.newFixedThreadPool(5); // 創建大小固定為 5 的線程池 Listtasks = new ArrayList<>(10); // tasks 為一組任務 for (int i = 0; i < 10; i++) { tasks.add(new AccumCallable(i * 10 + 1, (i + 1) * 10)); } List > futures = threadPool.invokeAll(tasks); // 阻塞,直到所有任務都運行完畢 int total = 0; for (Future future : futures) { total += future.get(); // 返回任務的結果 } System.out.println("Total: " + total); threadPool.shutdown(); // 向線程池發送關閉的指令 }
運行結果:
線程池是很強大而且很方便的工具,它提供了對線程進行統一的分配和調控的各種功能。自 JDK1.5 時 JDK 添加了線程池的功能之后,一般情況下更推薦使用線程池來編寫多線程程序,而不是直接使用 Thread。
(invokeAny 也是很實用的方法,請有興趣的讀者自己實踐)
References:
http://www.infoq.com/cn/artic...
http://www.cnblogs.com/absfre...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66387.html
摘要:一使用線程池的好處線程池提供了一種限制和管理資源包括執行一個任務。每個線程池還維護一些基本統計信息,例如已完成任務的數量。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。使用無界隊列作為線程池的工作隊列會對線程池帶來的影響與相同。 歷史優質文章推薦: Java并發編程指南專欄 分布式系統的經典基礎理論 可能是最漂亮的Spring事務管理詳解 面試中關于Java虛擬機(jvm)的問...
摘要:本人郵箱歡迎轉載轉載請注明網址代碼已經全部托管有需要的同學自行下載引言在之前的例子我們要創建多個線程處理一批任務的時候我是通過創建線程數組或者使用線程集合來管理的但是這樣做不太好因為這些線程沒有被重復利用所以這里要引入線程池今天我們就講線程 本人郵箱: 歡迎轉載,轉載請注明網址 http://blog.csdn.net/tianshi_kcogithub: https://github...
摘要:本文主要內容為簡單總結中線程池的相關信息。方法簇方法簇用于創建固定線程數的線程池。三種常見線程池的對比上文總結了工具類創建常見線程池的方法,現對三種線程池區別進行比較。 概述 線程可認為是操作系統可調度的最小的程序執行序列,一般作為進程的組成部分,同一進程中多個線程可共享該進程的資源(如內存等)。在單核處理器架構下,操作系統一般使用分時的方式實現多線程;在多核處理器架構下,多個線程能夠...
摘要:最后,我們會通過對源代碼的剖析深入了解線程池的運行過程和具體設計,真正達到知其然而知其所以然的水平。創建線程池既然線程池是一個類,那么最直接的使用方法一定是一個類的對象,例如。單線程線程池單線程線程 我們一般不會選擇直接使用線程類Thread進行多線程編程,而是使用更方便的線程池來進行任務的調度和管理。線程池就像共享單車,我們只要在我們有需要的時候去獲取就可以了。甚至可以說線程池更棒,...
摘要:用于限定中線程數的最大值。該線程池中的任務隊列維護著等待執行的對象。線程池和消息隊列筆者在實際工程應用中,使用過多線程和消息隊列處理過異步任務。以上是筆者在學習實踐之后對于多線程和消息隊列的粗淺認識,初學者切莫混淆兩者的作用。 多線程編程很難,難點在于多線程代碼的執行不是按照我們直覺上的執行順序。所以多線程編程必須要建立起一個宏觀的認識。 線程池是多線程編程中的一個重要概念。為了能夠更...
閱讀 3801·2023-04-26 02:07
閱讀 3679·2021-10-27 14:14
閱讀 2866·2021-10-14 09:49
閱讀 1631·2019-08-30 15:43
閱讀 2619·2019-08-29 18:33
閱讀 2375·2019-08-29 17:01
閱讀 922·2019-08-29 15:11
閱讀 592·2019-08-29 11:06