成員分爲四個部分:任務、任務執行、任務執行結果以及任務執行工具類java
任務:實現Callable
接口或Runnable
接口面試
任務執行部分:ThreadPoolExecutor
以及ScheduledThreadPoolExecutor
多線程
任務執行結果:Future
接口以及FutureTask
實現類框架
任務執行工廠類:Executors
異步
ThreadPoolExecutor(核心)ide
ThreadPoolExecutor
一般使用Executors
進行建立,其內部含有三種線程池:函數
FixedThreadPool
:含有固定線程數的線程池。工具
SingleThreadExecutor
:單線程的線程池,須要保證任務順序執行時採用。post
CachedThreadPool
:大小無界的線程池,只要須要線程就能夠一直建立線程。this
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor
一般使用Executors
進行建立,其內部含有兩種線程池:
ScheduledThreadPoolExecutor
:含有固定線程數的定時任務線程池SingleThreadScheduledExecutor
:只包含一個線程數的定時任務線程池,須要保證任務順序執行時採用。提交任務給任務執行框架執行後,任務執行框架會返回一個Future
接口類型的對象,該對象內部包含了任務執行結果信息。
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<> submit(Runnable task)
複製代碼
在JDK1.8返回的是FutureTask
對象,但不必定返回的就是FutureTask
對象,只保證返回Future
接口。
實現Runnable
或Callable
都可被任務執行框架執行,它們之間的區別是實現Runnable
的任務執行完成後不會返回結果,而實現Callable
接口的任務能夠返回結果。
返回結果爲null的封裝(沒有什麼意義)
public static Callable<Object> callable(Runnable task) 複製代碼
擁有返回結果的封裝
public static <T> Callable<T> callable(Runnable task, T result) 複製代碼
上一篇講過ThreadPoolExecutor構造函數中的參數含義,這裏再也不贅述,若是忘記了或者還沒閱讀過,能夠先去了解一下,兩篇文章是銜接的:juejin.im/post/5e9c45…
主要參數有4個:
corePolSize
:核心線程池大小maximumPoolSize
:最大線程池大小BlockingQueue
:存儲未執行任務的阻塞隊列RejectedExecutionHandler
:任務沒法被執行時的拒絕策略fixedThreadPool
稱爲可重用固定線程數的線程池,下面是構造該線程池的方法源碼:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
複製代碼
參數解讀
corePoolSize
和maximumPoolSize
都設置爲用戶傳入的nThreads
。keepAliveTime
設置爲0L
,表明只要空閒線程閒下來就立刻被終止,若是設置爲SECONDS > 0
,則表明線程空閒下來後等待新任務的最長時間SECONDS
。TimeUnit
表明keepAliveTime
的單位workQueue
設置阻塞隊列execute()執行流程
curThread
表明核心線程池的當前線程數
若是 curThread < corPoolSize
,那麼建立新線程執行任務
若是 curThread = corPoolSize
,那麼將任務加入阻塞隊列當中
線程池中的線程執行完任務後會空閒下來,而後會一直從阻塞隊列中獲取任務執行
使用無界阻塞隊列LinkedBlockingQueue
的後果:
curThread = corPoolSize
後,任務會一直加入到阻塞隊列,嚴重時阻塞隊列的任務數過多會形成GC內存溢出。maximumPoolSize
是一個無效參數。只有當任務沒法加入到阻塞隊列時知足curThread < maximumPoolSiize
纔會在線程池中建立新的線程執行該任務。keepAliveTime
參數無效curThread > maximumPoolSize
,也就不會出現任務拒絕執行的狀況,不會調用任務拒絕方法。GC內存溢出示例(我的理解,若是有錯,歡迎指出!):
public class Task implements Runnable {
private String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name);
}
}
public class FixedThreadPoolTest {
private static final ExecutorService fixThreadPool = Executors.newFixedThreadPool(10);
private static int i = 0;
public static void main(String[] args) {
// 不停地往線程池中提交任務
for (;i < 100000;i++) {
fixThreadPool.execute(new Task("任務" + i));
}
fixThreadPool.shutdown();
}
}
複製代碼
設置堆內存參數:-Xms2m -Xmx2m
拋出OOM異常,緣由在於阻塞隊列中的任務數過多。
使用單個Worker
線程的Executor
。本質上是fixedThreadPool
的corePoolSize
和maximumPoolSize
都設置爲1
,此外無其它區別。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
複製代碼
參數解釋
corePoolSize
和MaximumPoolSize
的參數都設置爲1
,其它參數含義和默認值都和fixedThreadPool
相同。fixedThreadPool
相同。execute()執行流程
Worker
線程來執行任務根據須要建立新線程的線程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
複製代碼
參數解釋
corePoolSize
設置爲0,即corePool
爲空maximumPoolSize
設置爲Integer.MAX_VALUE
,因此maximumPool
是幾乎無界的,可容納足夠多的線程keepAliveTime
設置爲60L,每一個空閒線程能夠等待新任務的時間是60秒TimeUnit
單位設置爲SECONDS
表明以秒爲單位計算SynchronousQueue
,每一次的offer
操做都要對應一個poll
操做execute()執行流程
offer
進阻塞隊列中poll
出任務執行,不然執行第3步poll
出任務執行。因爲JDK1.6與JDK1.8中延時隊列的實現不一樣,因此在閱讀本小節時不要死記硬背其實現,掌握其思想。圖的組件均會用中文表示,對思想的掌握有幫助,實現的思想是一致的。
該類用於指定特定的延遲時間後運行任務,或者按期執行任務。Timer
是單線程的,該類是多線程的,並且該類功能更增強大。
ScheduledThreadPoolExecutor將任務提交到延時隊列當中存儲,當任務到達執行時間時,Worker
工做線程會拉取任務進行執行,空閒的線程會一直阻塞,不斷嘗試獲取延時隊列中的任務。
ScheduledThreadPoolExecutor
本質上是ThreadPoolExecutor
的改造:
調用線程把待調度的任務(ScheduledFutureTask
)放入一個延時隊列當中,ScheduledFutureTask
的主要成員變量有三個:
time
:該任務要被執行的具體時間sequenceNumber
:該任務被添加到ScheduledThreadPoolExecutor
的序號,標記任務添加到隊列的時刻period
:任務執行的間隔週期延時隊列中採用優先級隊列進行排序。排序時,time
越小排在越前面,time
相同時,sequenceNumber
越小排在越前面。
ScheduledThreadPoolExecutor
執行任務的流程以下圖:
time
>= 當前時間time
變量爲下次要被執行的時間表示異步計算的結果,實現了Runnable、Future接口。
FutureTask的狀態轉換圖以下所示
FutureTask.run()
當任務處於未啓動狀態時,調用FutureTask.run()會使任務執行。
FutureTask.get()
FutureTask處於未啓動或已啓動狀態時,執行FutureTask.get()會致使線程阻塞;
當FutureTask處於已完成狀態時,調用FutureTask.get()會當即返回結果或者拋出異常。
FutureTask.cancel()
當任務處於未啓動狀態時,調用FutureTask.cancel()會致使此任務永遠不會被執行;
當任務處於已啓動狀態時,執行FutureTask.cancel(true)會中斷任務,執行FutureTask.cancel(false)方法將不會對正在執行此任務的線程產生任何影響;
當FutureTask處於完成狀態時,執行FutureTask.cancel(...)方法將返回false。
get()和cancel()的狀態轉換圖:
當一個線程須要等待另外一個線程把某個任務執行完後才能繼續執行,可使用FutureTask。這種用法並非很常見,只要知道有這種用法就能夠了。
這一章主要和上一章的線程池有很大聯繫,閱讀到這裏很是不容易,收穫也應該不少,留下幾道面試題來檢驗本身是否真的掌握了這些內容吧。
若是這篇文章能給你帶來一點點的小收穫,你的一個小小的點讚我會開心一成天!感謝你的閱讀!