Java多線程學習(八)線程池與Executor 框架
歷史優質文章推薦:
Java並發編程指南專欄
分布式系統的經典基礎理論
可能是最漂亮的Spring事務管理詳解
面試中關於Java虛擬機(jvm)的問題看這篇就夠了
目錄:
[TOC]
本節思維導圖:
思維導圖源文件+思維導圖軟件關註微信公眾號:“Java面試通關手冊” 回復關鍵字:“Java多線程” 免費領取。
一 使用線程池的好處
線程池提供了一種限制和管理資源(包括執行一個任務)。 每個線程池還維護一些基本統計信息,例如已完成任務的數量。
- 降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。
- 提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
- 提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。
##二 Executor 框架
2.1 簡介
Executor 框架是Java5之後引進的,在Java 5之後,通過 Executor 來啟動線程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用線程池實現,節約開銷)外,還有關鍵的一點:有助於避免 this 逃逸問題。
補充:this逃逸是指在構造函數返回之前其他線程就持有該對象的引用. 調用尚未構造完全的對象的方法可能引發令人疑惑的錯誤。
2.2 Executor 框架結構(主要由三大部分組成)
####1 任務。
執行任務需要實現的Runnable接口或Callable接口。
Runnable接口或Callable接口實現類都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor執行。
兩者的區別:
Runnable接口不會返回結果但是Callable接口可以返回結果。後面介紹Executors類的一些方法的時候會介紹到兩者的相互轉換。
####2 任務的執行
如下圖所示,包括任務執行機制的核心接口Executor ,以及繼承自Executor 接口的ExecutorService接口。ScheduledThreadPoolExecutor和ThreadPoolExecutor這兩個關鍵類實現了ExecutorService接口。註意: 通過查看ScheduledThreadPoolExecutor源代碼我們發現ScheduledThreadPoolExecutor實際上是繼承了ThreadPoolExecutor並實現了ScheduledExecutorService ,而ScheduledExecutorService又實現了ExecutorService,正如我們下面給出的類關系圖顯示的一樣。
ThreadPoolExecutor類描述:
//AbstractExecutorService實現了ExecutorService接口
public class ThreadPoolExecutor extends AbstractExecutorService
ScheduledThreadPoolExecutor類描述:
//ScheduledExecutorService實現了ExecutorService接口
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService
####3 異步計算的結果
Future接口以及Future接口的實現類FutureTask類。
當我們把Runnable接口或Callable接口的實現類提交(調用submit方法)給ThreadPoolExecutor或ScheduledThreadPoolExecutor時,會返回一個FutureTask對象。
我們以AbstractExecutorService接口中的一個submit方法為例子來看看源代碼:
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
上面方法調用的newTaskFor方法返回了一個FutureTask對象。
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
2.3 Executor 框架的使用示意圖
-
主線程首先要創建實現Runnable或者Callable接口的任務對象。
備註: 工具類Executors可以實現Runnable對象和Callable對象之間的相互轉換。(Executors.callable(Runnable task)或Executors.callable(Runnable task,Object resule))。 -
然後可以把創建完成的Runnable對象直接交給ExecutorService執行(ExecutorService.execute(Runnable command));或者也可以把Runnable對象或Callable對象提交給ExecutorService執行(ExecutorService.submit(Runnable task)或ExecutorService.submit(Callable <T> task))。
執行execute()方法和submit()方法的區別是什麽呢?
1)execute()方法用於提交不需要返回值的任務,所以無法判斷任務是否被線程池執行成功與否;
2)submit()方法用於提交需要返回值的任務。線程池會返回一個future類型的對象,通過這個future對象可以判斷任務是否執行成功,並且可以通過future的get()方法來獲取返回值,get()方法會阻塞當前線程直到任務完成,而使用get(long timeout,TimeUnit unit)方法則會阻塞當前線程一段時間後立即返回,這時候有可能任務沒有執行完。 -
如果執行ExecutorService.submit(…),ExecutorService將返回一個實現Future接口的對象(我們剛剛也提到過了執行execute()方法和submit()方法的區別,到目前為止的JDK中,返回的是FutureTask對象)。由於FutureTask實現了Runnable,程序員也可以創建FutureTask,然後直接交給ExecutorService執行。
- 最後,主線程可以執行FutureTask.get()方法來等待任務執行完成。主線程也可以執行FutureTask.cancel(boolean mayInterruptIfRunning)來取消此任務的執行。
三 ThreadPoolExecutor詳解
線程池實現類ThreadPoolExecutor是Executor 框架最核心的類,先來看一下這個類中比較重要的四個屬性
3.1 ThreadPoolExecutor類的四個比較重要的屬性
3.2 ThreadPoolExecutor類中提供的四個構造方法
我們看最長的那個,其余三個都是在這個構造方法的基礎上產生(給定某些默認參數的構造方法)
/**
* 用給定的初始參數創建一個新的ThreadPoolExecutor。
* @param keepAliveTime 當線程池中的線程數量大於corePoolSize的時候,如果這時沒有新的任務提交,
*核心線程外的線程不會立即銷毀,而是會等待,直到等待的時間超過了keepAliveTime;
* @param unit keepAliveTime參數的時間單位
* @param workQueue 等待隊列,當任務提交時,如果線程池中的線程數量大於等於corePoolSize的時候,把該任務封裝成一個Worker對象放入等待隊列;
*
* @param threadFactory 執行者創建新線程時使用的工廠
* @param handler RejectedExecutionHandler類型的變量,表示線程池的飽和策略。
* 如果阻塞隊列滿了並且沒有空閑的線程,這時如果繼續提交任務,就需要采取一種策略處理該任務。
* 線程池提供了4種策略:
1.AbortPolicy:直接拋出異常,這是默認策略;
2.CallerRunsPolicy:用調用者所在的線程來執行任務;
3.DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,並執行當前任務;
4.DiscardPolicy:直接丟棄任務;
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
3.3 如何創建ThreadPoolExecutor
方式一:通過構造方法實現(官方API文檔並不推薦,所以建議使用第二種方式)
方式二:通過Executor 框架的工具類Executors來實現
我們可以創建三種類型的ThreadPoolExecutor:
- FixedThreadPool
- SingleThreadExecutor
- CachedThreadPool
對應Executors工具類中的方法如圖所示:
3.4 FixedThreadPool詳解
FixedThreadPool被稱為可重用固定線程數的線程池。通過Executors類中的相關源代碼來看一下相關實現:
/**
* 創建一個可重用固定數量線程的線程池
*在任何時候至多有n個線程處於活動狀態
*如果在所有線程處於活動狀態時提交其他任務,則它們將在隊列中等待,
*直到線程可用。 如果任何線程在關閉之前的執行期間由於失敗而終止,
*如果需要執行後續任務,則一個新的線程將取代它。池中的線程將一直存在
*知道調用shutdown方法
* @param nThreads 線程池中的線程數
* @param threadFactory 創建新線程時使用的factory
* @return 新創建的線程池
* @throws NullPointerException 如果threadFactory為null
* @throws IllegalArgumentException if {@code nThreads <= 0}
*/
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
另外還有一個FixedThreadPool的實現方法,和上面的類似,所以這裏不多做闡述:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
從上面源代碼可以看出新創建的FixedThreadPool的corePoolSize和maximumPoolSize都被設置為nThreads。
FixedThreadPool的execute()方法運行示意圖(該圖片來源:《Java並發編程的藝術》):
上圖說明:
- 如果當前運行的線程數小於corePoolSize,則創建新的線程來執行任務;
- 當前運行的線程數等於corePoolSize後,將任務加入LinkedBlockingQueue;
- 線程執行完1中的任務後,會在循環中反復從LinkedBlockingQueue中獲取任務來執行;
FixedThreadPool使用×××隊列 LinkedBlockingQueue(隊列的容量為Intger.MAX_VALUE)作為線程池的工作隊列會對線程池帶來如下影響:
- 當線程池中的線程數達到corePoolSize後,新任務將在×××隊列中等待,因此線程池中的線程數不會超過corePoolSize;
- 由於1,使用×××隊列時maximumPoolSize將是一個無效參數;
- 由於1和2,使用×××隊列時keepAliveTime將是一個無效參數;
- 運行中的FixedThreadPool(未執行shutdown()或shutdownNow()方法)不會拒絕任務
3.5 SingleThreadExecutor詳解
SingleThreadExecutor是使用單個worker線程的Executor。下面看看SingleThreadExecutor的實現:
/** *創建使用單個worker線程運行×××隊列的Executor *並使用提供的ThreadFactory在需要時創建新線程 * * @param threadFactory 創建新線程時使用的factory * * @return 新創建的單線程Executor * @throws NullPointerException 如果ThreadFactory為空 */ public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
從上面源代碼可以看出新創建的SingleThreadExecutor的corePoolSize和maximumPoolSize都被設置為1.其他參數和FixedThreadPool相同。SingleThreadExecutor使用×××隊列LinkedBlockingQueue作為線程池的工作隊列(隊列的容量為Intger.MAX_VALUE)。SingleThreadExecutor使用×××隊列作為線程池的工作隊列會對線程池帶來的影響與FixedThreadPool相同。
SingleThreadExecutor的運行示意圖(該圖片來源:《Java並發編程的藝術》):
上圖說明;
- 如果當前運行的線程數少於corePoolSize,則創建一個新的線程執行任務;
- 當前線程池中有一個運行的線程後,將任務加入LinkedBlockingQueue
- 線程執行完1中的任務後,會在循環中反復從LinkedBlockingQueue中獲取任務來執行;
3.6 CachedThreadPool詳解
CachedThreadPool是一個會根據需要創建新線程的線程池。下面通過源碼來看看 CachedThreadPool的實現:
/**
* 創建一個線程池,根據需要創建新線程,但會在先前構建的線程可用時重用它,
*並在需要時使用提供的ThreadFactory創建新線程。
* @param threadFactory 創建新線程使用的factory
* @return 新創建的線程池
* @throws NullPointerException 如果threadFactory為空
*/
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
CachedThreadPool的corePoolSize被設置為空(0),maximumPoolSize被設置為Integer.MAX.VALUE,即它是×××的,這也就意味著如果主線程提交任務的速度高於maximumPool中線程處理任務的速度時,CachedThreadPool會不斷創建新的線程。極端情況下,這樣會導致耗盡cpu和內存資源。
CachedThreadPool的execute()方法的執行示意圖(該圖片來源:《Java並發編程的藝術》):
上圖說明:
- 首先執行SynchronousQueue.offer(Runnable task)。如果當前maximumPool中有閑線程正在執行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那麽主線程執行offer操作與空閑線程執行的poll操作配對成功,主線程把任務交給空閑線程執行,execute()方法執行完成,否則執行下面的步驟2;
- 當初始maximumPool為空,或者maximumPool中沒有空閑線程時,將沒有線程執行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。這種情況下,步驟1將失敗,此時CachedThreadPool會創建新線程執行任務,execute方法執行完成;
3.7 ThreadPoolExecutor使用示例
3.7.1 示例代碼
首先創建一個Runnable接口的實現類(當然也可以是Callable接口,我們上面也說了兩者的區別是:Runnable接口不會返回結果但是Callable接口可以返回結果。後面介紹Executors類的一些方法的時候會介紹到兩者的相互轉換。)
import java.util.Date;
/**
* 這是一個簡單的Runnable類,需要大約5秒鐘來執行其任務。
*/
public class WorkerThread implements Runnable {
private String command;
public WorkerThread(String s) {
this.command = s;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());
processCommand();
System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());
}
private void processCommand() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return this.command;
}
}
編寫測試程序,我們這裏以FixedThreadPool為例子
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
//創建一個FixedThreadPool對象
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
//創建WorkerThread對象(WorkerThread類實現了Runnable 接口)
Runnable worker = new WorkerThread("" + i);
//執行Runnable
executor.execute(worker);
}
//終止線程池
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("Finished all threads");
}
}
輸出示例:
pool-1-thread-5 Start. Time = Thu May 31 10:22:52 CST 2018
pool-1-thread-3 Start. Time = Thu May 31 10:22:52 CST 2018
pool-1-thread-2 Start. Time = Thu May 31 10:22:52 CST 2018
pool-1-thread-4 Start. Time = Thu May 31 10:22:52 CST 2018
pool-1-thread-1 Start. Time = Thu May 31 10:22:52 CST 2018
pool-1-thread-4 End. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-1 End. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-2 End. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-5 End. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-3 End. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-5 Start. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-2 Start. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-1 Start. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-4 Start. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-3 Start. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-5 End. Time = Thu May 31 10:23:02 CST 2018
pool-1-thread-1 End. Time = Thu May 31 10:23:02 CST 2018
pool-1-thread-2 End. Time = Thu May 31 10:23:02 CST 2018
pool-1-thread-3 End. Time = Thu May 31 10:23:02 CST 2018
pool-1-thread-4 End. Time = Thu May 31 10:23:02 CST 2018
Finished all threads
3.7.2 shutdown()VS shutdownNow()
shutdown()方法表明關閉已在Executor上調用,因此不會再向DelayedPool添加任何其他任務(由ScheduledThreadPoolExecutor類在內部使用)。 但是,已經在隊列中提交的任務將被允許完成。
另一方面,shutdownNow()方法試圖終止當前正在運行的任務,並停止處理排隊的任務並返回正在等待執行的List。
####3.7.3 isTerminated() Vs isShutdown()
isShutdown()表示執行程序正在關閉,但並非所有任務都已完成執行。
另一方面,isShutdown()表示所有線程都已完成執行。
四 ScheduledThreadPoolExecutor詳解
4.1 簡介
ScheduledThreadPoolExecutor主要用來在給定的延遲後運行任務,或者定期執行任務。
ScheduledThreadPoolExecutor使用的任務隊列DelayQueue封裝了一個PriorityQueue,PriorityQueue會對隊列中的任務進行排序,執行所需時間短的放在前面先被執行(ScheduledFutureTask的time變量小的先執行),如果執行所需時間相同則先提交的任務將被先執行(ScheduledFutureTask的squenceNumber變量小的先執行)。
ScheduledThreadPoolExecutor和Timer的比較:
- Timer對系統時鐘的變化敏感,ScheduledThreadPoolExecutor不是;
- Timer只有一個執行線程,因此長時間運行的任務可以延遲其他任務。 ScheduledThreadPoolExecutor可以配置任意數量的線程。 此外,如果你想(通過提供ThreadFactory),你可以完全控制創建的線程;
- 在TimerTask中拋出的運行時異常會殺死一個線程,從而導致Timer死機:-( ...即計劃任務將不再運行。ScheduledThreadExecutor不僅捕獲運行時異常,還允許您在需要時處理它們(通過重寫afterExecute方法 ThreadPoolExecutor)。拋出異常的任務將被取消,但其他任務將繼續運行。
綜上,在JDK1.5之後,你沒有理由再使用Timer進行任務調度了。
備註: Quartz是一個由java編寫的任務調度庫,由OpenSymphony組織開源出來。在實際項目開發中使用Quartz的還是居多,比較推薦使用Quartz。因為Quartz理論上能夠同時對上萬個任務進行調度,擁有豐富的功能特性,包括任務調度、任務持久化、可集群化、插件等等。
4.2 ScheduledThreadPoolExecutor運行機制
ScheduledThreadPoolExecutor的執行主要分為兩大部分:
- 當調用ScheduledThreadPoolExecutor的 scheduleAtFixedRate() 方法或者scheduleWirhFixedDelay() 方法時,會向ScheduledThreadPoolExecutor的 DelayQueue 添加一個實現了 RunnableScheduledFutur 接口的 ScheduledFutureTask 。
- 線程池中的線程從DelayQueue中獲取ScheduledFutureTask,然後執行任務。
ScheduledThreadPoolExecutor為了實現周期性的執行任務,對ThreadPoolExecutor做了如下修改:
- 使用 DelayQueue 作為任務隊列;
- 獲取任務的方不同
- 執行周期任務後,增加了額外的處理
4.3 ScheduledThreadPoolExecutor執行周期任務的步驟
- 線程1從DelayQueue中獲取已到期的ScheduledFutureTask(DelayQueue.take())。到期任務是指ScheduledFutureTask的time大於等於當前系統的時間;
- 線程1執行這個ScheduledFutureTask;
- 線程1修改ScheduledFutureTask的time變量為下次將要被執行的時間;
- 線程1把這個修改time之後的ScheduledFutureTask放回DelayQueue中(DelayQueue.add())。
4.4 ScheduledThreadPoolExecutor使用示例
-
創建一個簡單的實現Runnable接口的類(我們上面的例子已經實現過)
-
測試程序使用ScheduledExecutorService和ScheduledThreadPoolExecutor實現的java調度。
/** * 使用ScheduledExecutorService和ScheduledThreadPoolExecutor實現的java調度程序示例程序。 */ public class ScheduledThreadPoolDemo { public static void main(String[] args) throws InterruptedException { //創建一個ScheduledThreadPoolExecutor對象 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); //計劃在某段時間後運行 System.out.println("Current Time = "+new Date()); for(int i=0; i<3; i++){ Thread.sleep(1000); WorkerThread worker = new WorkerThread("do heavy processing"); //創建並執行在給定延遲後啟用的單次操作。 scheduledThreadPool.schedule(worker, 10, TimeUnit.SECONDS); } //添加一些延遲讓調度程序產生一些線程 Thread.sleep(30000); System.out.println("Current Time = "+new Date()); //關閉線程池 scheduledThreadPool.shutdown(); while(!scheduledThreadPool.isTerminated()){ //等待所有任務完成 } System.out.println("Finished all threads"); }
}
運行結果:
Current Time = Wed May 30 17:11:16 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:11:27 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:11:28 CST 2018
pool-1-thread-3 Start. Time = Wed May 30 17:11:29 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:11:32 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:11:33 CST 2018
pool-1-thread-3 End. Time = Wed May 30 17:11:34 CST 2018
Current Time = Wed May 30 17:11:49 CST 2018
Finished all threads
#### 4.4.1 ScheduledExecutorService scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)方法
我們可以使用ScheduledExecutorService scheduleAtFixedRate方法來安排任務在初始延遲後運行,然後在給定的時間段內運行。
時間段是從池中第一個線程的開始,因此如果您將period指定為1秒並且線程運行5秒,那麽只要第一個工作線程完成執行,下一個線程就會開始執行。
```java
for (int i = 0; i < 3; i++) {
Thread.sleep(1000);
WorkerThread worker = new WorkerThread("do heavy processing");
// schedule task to execute at fixed rate
scheduledThreadPool.scheduleAtFixedRate(worker, 0, 10,
TimeUnit.SECONDS);
}
輸出示例:
Current Time = Wed May 30 17:47:09 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:47:10 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:47:11 CST 2018
pool-1-thread-3 Start. Time = Wed May 30 17:47:12 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:47:15 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:47:16 CST 2018
pool-1-thread-3 End. Time = Wed May 30 17:47:17 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:47:20 CST 2018
pool-1-thread-4 Start. Time = Wed May 30 17:47:21 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:47:22 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:47:25 CST 2018
pool-1-thread-4 End. Time = Wed May 30 17:47:26 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:47:27 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:47:30 CST 2018
pool-1-thread-3 Start. Time = Wed May 30 17:47:31 CST 2018
pool-1-thread-5 Start. Time = Wed May 30 17:47:32 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:47:35 CST 2018
pool-1-thread-3 End. Time = Wed May 30 17:47:36 CST 2018
pool-1-thread-5 End. Time = Wed May 30 17:47:37 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:47:40 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:47:41 CST 2018
Current Time = Wed May 30 17:47:42 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:47:45 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:47:46 CST 2018
Finished all threads
Process finished with exit code 0
4.4.2 ScheduledExecutorService scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit)方法
ScheduledExecutorService scheduleWithFixedDelay方法可用於以初始延遲啟動周期性執行,然後以給定延遲執行。 延遲時間是線程完成執行的時間。
for (int i = 0; i < 3; i++) {
Thread.sleep(1000);
WorkerThread worker = new WorkerThread("do heavy processing");
scheduledThreadPool.scheduleWithFixedDelay(worker, 0, 1,
TimeUnit.SECONDS);
}
輸出示例:
Current Time = Wed May 30 17:58:09 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:58:10 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:58:11 CST 2018
pool-1-thread-3 Start. Time = Wed May 30 17:58:12 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:58:15 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:58:16 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:58:16 CST 2018
pool-1-thread-3 End. Time = Wed May 30 17:58:17 CST 2018
pool-1-thread-4 Start. Time = Wed May 30 17:58:17 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:58:18 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:58:21 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:58:22 CST 2018
pool-1-thread-4 End. Time = Wed May 30 17:58:22 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:58:23 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:58:23 CST 2018
pool-1-thread-4 Start. Time = Wed May 30 17:58:24 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:58:27 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:58:28 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:58:28 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:58:29 CST 2018
pool-1-thread-4 End. Time = Wed May 30 17:58:29 CST 2018
pool-1-thread-4 Start. Time = Wed May 30 17:58:30 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:58:33 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:58:34 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:58:34 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:58:35 CST 2018
pool-1-thread-4 End. Time = Wed May 30 17:58:35 CST 2018
pool-1-thread-4 Start. Time = Wed May 30 17:58:36 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:58:39 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:58:40 CST 2018
pool-1-thread-5 Start. Time = Wed May 30 17:58:40 CST 2018
pool-1-thread-4 End. Time = Wed May 30 17:58:41 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:58:41 CST 2018
Current Time = Wed May 30 17:58:42 CST 2018
pool-1-thread-5 End. Time = Wed May 30 17:58:45 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:58:46 CST 2018
Finished all threads
4.4.3 scheduleWithFixedDelay() vs scheduleAtFixedRate()
scheduleAtFixedRate(...)將延遲視為兩個任務開始之間的差異(即定期調用)
scheduleWithFixedDelay(...)將延遲視為一個任務結束與下一個任務開始之間的差異
scheduleAtFixedRate(): 創建並執行在給定的初始延遲之後,隨後以給定的時間段首先啟用的周期性動作; 那就是執行將在initialDelay之後開始,然後initialDelay+period ,然後是initialDelay + 2 * period ,等等。 如果任務的執行遇到異常,則後續的執行被抑制。 否則,任務將僅通過取消或終止執行人終止。 如果任務執行時間比其周期長,則後續執行可能會遲到,但不會同時執行。
scheduleWithFixedDelay() : 創建並執行在給定的初始延遲之後首先啟用的定期動作,隨後在一個執行的終止和下一個執行的開始之間給定的延遲。 如果任務的執行遇到異常,則後續的執行被抑制。 否則,任務將僅通過取消或終止執行終止。
五 各種線程池的適用場景介紹
FixedThreadPool: 適用於為了滿足資源管理需求,而需要限制當前線程數量的應用場景。它適用於負載比較重的服務器;
SingleThreadExecutor: 適用於需要保證順序地執行各個任務並且在任意時間點,不會有多個線程是活動的應用場景。
CachedThreadPool: 適用於執行很多的短期異步任務的小程序,或者是負載較輕的服務器;
ScheduledThreadPoolExecutor: 適用於需要多個後臺執行周期任務,同時為了滿足資源管理需求而需要限制後臺線程的數量的應用場景,
SingleThreadScheduledExecutor: 適用於需要單個後臺線程執行周期任務,同時保證順序地執行各個任務的應用場景。
六 總結
本節只是簡單的介紹了一下使用線程池的好處,然後花了大量篇幅介紹Executor 框架。詳細介紹了Executor 框架中ThreadPoolExecutor和ScheduledThreadPoolExecutor,並且通過實例詳細講解了ScheduledThreadPoolExecutor的使用。對於FutureTask 只是粗略帶過,因為篇幅問題,並沒有深究它的原理,後面的文章會進行補充。這一篇文章只是大概帶大家過一下線程池的基本概覽,深入講解的地方不是很多,後續會通過源碼深入研究其中比較重要的一些知識點。
最後,就是這兩周要考試了,會抽點時間出來簡單應付一下學校考試了。然後,就是寫這篇多線程的文章廢了好多好多時間。一直不知從何寫起。
參考
《Java並發編程的藝術》
Java Scheduler ScheduledExecutorService ScheduledThreadPoolExecutor Example
java.util.concurrent.ScheduledThreadPoolExecutor Example
ThreadPoolExecutor – Java Thread Pool Example
Java多線程學習(八)線程池與Executor 框架