?
java多線程-概念&創建啟動&中斷&守護線程&優先級&線程狀態(多線程編程之一)
java多線程同步以及線程間通信詳解&消費者生產者模式&死鎖&Thread.join()(多線程編程之二)
java&android線程池-Executor框架之ThreadPoolExcutor&ScheduledThreadPoolExecutor淺析(多線程編程之三)
Java多線程:Callable、Future和FutureTask淺析(多線程編程之四)
?
無論是在java還是在android中其實使用到的線程池都基本是一樣的,因此本篇我們將來認識一下線程池Executor框架(相關知識點結合了并發編程藝術書以及Android開發藝術探索而總結),下面是本篇的主要知識點:
?
1.Executor框架淺析?
首先我們得明白一個 問題,為什么需要線程池?在java中,使用線程來執行異步任務時,線程的創建和銷毀需要一定的開銷,如果我們為每一個任務創建一個新的線程來執行的話,那么這些線程的創建與銷毀將消耗大量的計算資源。同時為每一個任務創建一個新線程來執行,這樣的方式可能會使處于高負荷狀態的應用最終崩潰。所以線程池的出現為解決這個問題帶來曙光。我們將在線程池中創建若干條線程,當有任務需要執行時就從該線程池中獲取一條線程來執行任務,如果一時間任務過多,超出線程池的線程數量,那么后面的線程任務就進入一個等待隊列進行等待,直到線程池有線程處于空閑時才從等待隊列獲取要執行的任務進行處理,以此循環.....這樣就大大減少了線程創建和銷毀的開銷,也會緩解我們的應用處于超負荷時的情況。
1.1Executor框架的兩級調度模型
在java線程啟動時會創建一個本地操作系統線程,當該java線程終止時,這個操作系統線程也會被回收。而每一個java線程都會被一對一映射為本地操作系統的線程,操作系統會調度所有的線程并將它們分別給可用的CPU。而所謂的映射方式是這樣實現的,在上層,java多線程程序通過把應用分為若干個任務,然后使用用戶級的調度器(Executor框架)將這些任務映射為固定數量的線程;在底層,操作系統內核將這些線程映射到硬件處理器上。這樣種兩級調度模型如下圖所示:
?
?
從圖中我們可以看出,應用程序通過Executor框架控制上層的調度,而下層的調度由操作系統內核控制,下層的調度不受應用程序的控制。
1.2 Executor框架的結構
Executor框架的結構主要包括3個部分
1.任務:包括被執行任務需要實現的接口:Runnable接口或Callable接口
2.任務的執行:包括任務執行機制的核心接口Executor,以及繼承自Executor的EexcutorService接口。Exrcutor有兩個關鍵類實現了ExecutorService接口(ThreadPoolExecutor和ScheduledThreadPoolExecutor)。
3.異步計算的結果:包括接口Future和實現Future接口的FutureTask類(這個我們放在下一篇文章說明)
下面我們通過一個UML圖來認識一下這些類間的關系:
?
?
Extecutor是一個接口,它是Executor框架的基礎,它將任務的提交與任務的執行分離開來。
ThreadPoolExecutor是線程池的核心實現類,用來執行被提交的任務。
ScheduledThreadPoolExecutor是一個實現類,可以在給定的延遲后運行命令,或者定期執行命令。ScheduledThreadPoolExecutor比Timer更靈活,功能更強大。
Future接口和實現Future接口的FutureTask類,代表異步計算的結果。
Runnable接口和Callable接口的實現類,都可以被ThreadPoolExecutor或者ScheduledThreadPoolExecutor執行。區別就是Runnable無法返回執行結果,而Callable可以返回執行結果。
下面我們通過一張圖來理解它們間的執行關系:
?
?
分析說明:
主線程首先創建實現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)。這里需要注意的是如果執行ExecutorService.submit(...),ExecutorService將返回一個實現Future接口的對象(其實就是FutureTask)。當然由于FutureTask實現了Runnable接口,我們也可以直接創建FutureTask,然后提交給ExecutorService執行。到此Executor框架的主要體系結構我們都介紹完了,我們對此有了大概了解后,下面我們就重點聊聊兩個主要的線程池實現類。
2.ThreadPoolExecutor淺析?
ThreadPoolExecutor是線程的真正實現,通常使用工廠類Executors來創建,但它的構造方法提供了一系列參數來配置線程池,下面我們就先介紹ThreadPoolExecutor的構造方法中各個參數的含義。
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler);}
corePoolSize:線程池的核心線程數,默認情況下,核心線程數會一直在線程池中存活,即使它們處理閑置狀態。如果將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置為true,那么閑置的核心線程在等待新任務到來時會執行超時策略,這個時間間隔由keepAliveTime所指定,當等待時間超過keepAliveTime所指定的時長后,核心線程就會被終止。
ExecutorService fixedThreadPool=Executors.newFixedThreadPool(5);
我們來看看FixedThreadPool創建方法源碼:
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}
?
FixedThreadPool的corePoolSize和maximumPoolSize參數都被設置為nThreads。當線程池中的線程數量大于corePoolSize時,keepAliveTime為非核心空閑線程等待新任務的最長時間,超過這個時間后非核心線程將被終止,這里keepAliveTime設置為0L,就說明非核心線程會立即被終止。事實上這里也沒有非核心線程創建,因為核心線程數和最大線程數都一樣的。下面我們來看看FixedThreadPool的execute()方法的運行流程:
?
?
public class LiftOff implements Runnable{ protected int countDown = 10; //Default private static int taskCount = 0; private final int id = taskCount++; public LiftOff() {} public LiftOff(int countDown) { this.countDown = countDown; } public String status() { return "#" + id + "(" + (countDown > 0 ? countDown : "LiftOff!") + ") "; } @Override public void run() { while(countDown-- > 0) { System.out.print(status()); Thread.yield(); } }
}
?
聲明一個Runnable對象,使用FixedThreadPool執行任務如下:
public class FixedThreadPool { public static void main(String[] args) { //三個線程來執行五個任務 ExecutorService exec = Executors.newFixedThreadPool(3); for(int i = 0; i < 5; i++) { exec.execute(new LiftOff()); } exec.shutdown(); }
}
ExecutorService cachedThreadPool=Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}
?
從該靜態方法,我們可以看到CachedThreadPool的corePoolSize被設置為0,而maximumPoolSize被設置Integer.MAX_VALUE,即maximumPoolSize是無界的,而keepAliveTime被設置為60L,單位為妙。也就是空閑線程等待時間最長為60秒,超過該時間將會被終止。而且在這里CachedThreadPool使用的是沒有容量的SynchronousQueue作為線程池的工作隊列,但其maximumPoolSize是無界的,也就是意味著如果主線程提交任務的速度高于maximumPoolSize中線程處理任務的速度時CachedThreadPool將會不斷的創建新的線程,在極端情況下,CachedThreadPool會因為創建過多線程而耗盡CPU和內存資源。CachedThreadPool的execute()方法的運行流程:
?
CachedThreadPool使用的案例代碼如下:
public class CachedThreadPool { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < 10; i++) { exec.execute(new LiftOff()); } exec.shutdown(); }
}
?
SingleThreadExecutor模式只會創建一個線程。它和FixedThreadPool比較類似,不過線程數是一個。如果多個任務被提交給SingleThreadExecutor的話,那么這些任務會被保存在一個隊列中,并且會按照任務提交的順序,一個先執行完成再執行另外一個線程。SingleThreadExecutor模式可以保證只有一個任務會被執行。這種特點可以被用來處理共享資源的問題而不需要考慮同步的問題。
ExecutorService singleThreadExecutor=Executors.newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}
?
從靜態方法可以看出SingleThreadExecutor的corePoolSize和maximumPoolSize被設置為1,其他參數則與FixedThreadPool相同。SingleThreadExecutor使用的工作隊列也是無界隊列LinkedBlockingQueue。由于SingleThreadExecutor采用無界隊列的對線程池的影響與FixedThreadPool一樣,這里就不過多描述了。同樣的我們先來看看其運行流程:
?
public class SingleThreadExecutor { public static void main(String[] args) { ExecutorService exec = Executors.newSingleThreadExecutor(); for (int i = 0; i < 2; i++) { exec.execute(new LiftOff()); } }
}
?
?
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
創建SingleThreadScheduledExecutor的方法構造如下:
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
創建實例對象代碼如下:
ScheduledExecutorService scheduledThreadPoolExecutor=Executors.newScheduledThreadPool(5);
ScheduledExecutorService singleThreadScheduledExecutor=Executors.newSingleThreadScheduledExecutor();
public ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);
package com.zejian.Executor;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
/*** @author zejian* @time 2016年3月14日 下午9:10:41* @decrition 創建一個工作線程繼承Runnable*/
public class WorkerThread implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+" Start. Time = "+getNowDate());threadSleep();System.out.println(Thread.currentThread().getName()+" End. Time = "+getNowDate());}/*** 睡3秒*/public void threadSleep(){try {Thread.sleep(3000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}/*** 獲取現在時間* * @return 返回時間類型 yyyy-MM-dd HH:mm:ss*/public static String getNowDate() {Date currentTime = new Date();SimpleDateFormat formatter; formatter = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss"); String ctime = formatter.format(currentTime); return ctime;}
}
?
執行類如下:
package com.zejian.Executor;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/*** @author zejian* @time 2016年3月14日 下午9:27:06* @decrition 執行類*/
public class ScheduledThreadPoolTest {public static void main(String[] args) {ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);try {//schedule to run after sometimeSystem.out.println("Current Time = "+getNowDate());for(int i=0; i<3; i++){Thread.sleep(1000);WorkerThread worker = new WorkerThread();//延遲10秒后執行scheduledThreadPool.schedule(worker, 10, TimeUnit.SECONDS);}Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}scheduledThreadPool.shutdown();while(!scheduledThreadPool.isTerminated()){//wait for all tasks to finish}System.out.println("Finished all threads");}/*** 獲取現在時間* * @return 返回時間類型 yyyy-MM-dd HH:mm:ss*/public static String getNowDate() {Date currentTime = new Date();SimpleDateFormat formatter; formatter = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss"); String ctime = formatter.format(currentTime); return ctime;}
}
運行輸入執行結果:
?
線程任務確實在10秒延遲后才開始執行。這就是schedule()方法的使用。下面我們再介紹2個可用于周期性執行任務的方法。
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)
scheduleAtFixedRate方法的作用是預定在初始的延遲結束后,周期性地執行給定的任務,周期長度為period,其中initialDelay為初始延遲。
(按照固定的時間來執行,即:到點執行)
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);
package com.zejian.Executor;
import java.util.Date;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/*** @author zejian* @time 2016年3月14日 下午10:05:07* @decrition 周期函數測試類*/
public class ScheduledTask {public ScheduledThreadPoolExecutor se = new ScheduledThreadPoolExecutor(5);public static void main(String[] args) {new ScheduledTask();}public void fixedPeriodSchedule() {// 設定可以循環執行的runnable,初始延遲為0,這里設置的任務的間隔為5秒for(int i=0;i<5;i++){se.scheduleAtFixedRate(new FixedSchedule(), 0, 5, TimeUnit.SECONDS);}}public ScheduledTask() {fixedPeriodSchedule();}class FixedSchedule implements Runnable {public void run() {System.out.println("當前線程:"+Thread.currentThread().getName()+" 當前時間:"+new Date(System.currentTimeMillis()));}}
}
?
當前線程:pool-1-thread-5 當前時間:Tue Aug 08 09:43:18 CST 2017
當前線程:pool-1-thread-4 當前時間:Tue Aug 08 09:43:18 CST 2017
當前線程:pool-1-thread-3 當前時間:Tue Aug 08 09:43:18 CST 2017
當前線程:pool-1-thread-1 當前時間:Tue Aug 08 09:43:18 CST 2017
當前線程:pool-1-thread-2 當前時間:Tue Aug 08 09:43:18 CST 2017
當前線程:pool-1-thread-1 當前時間:Tue Aug 08 09:43:23 CST 2017
當前線程:pool-1-thread-4 當前時間:Tue Aug 08 09:43:23 CST 2017
當前線程:pool-1-thread-3 當前時間:Tue Aug 08 09:43:23 CST 2017
當前線程:pool-1-thread-5 當前時間:Tue Aug 08 09:43:23 CST 2017
當前線程:pool-1-thread-2 當前時間:Tue Aug 08 09:43:23 CST 2017
當前線程:pool-1-thread-1 當前時間:Tue Aug 08 09:43:28 CST 2017
當前線程:pool-1-thread-4 當前時間:Tue Aug 08 09:43:28 CST 2017
當前線程:pool-1-thread-5 當前時間:Tue Aug 08 09:43:28 CST 2017
當前線程:pool-1-thread-3 當前時間:Tue Aug 08 09:43:28 CST 2017
當前線程:pool-1-thread-1 當前時間:Tue Aug 08 09:43:28 CST 2017
?
主要參考書籍:
?
java核心技術卷1
android開發藝術探索
java并發編程的藝術
---------------------
作者:zejian_
來源:CSDN
原文:https://blog.csdn.net/javazejian/article/details/50890554
版權聲明:本文為作者原創文章,轉載請附上博文鏈接!
內容解析By:CSDN,CNBLOG博客文章一鍵轉載插件