Executor框架提供了各種類型的線程池。
ExecutorService 繼承了Executor接口,初步定義了線程池內的方法。然後 ,AbstractExecutorService又實現了ExecutorService裏面定義的方法。
再最後ThreadPoolExecutor 繼承了AbstractExecutorService,裏面是更爲細緻的實現。
jdk定義了一個Executors工廠類,裏面對ThreadPoolExecutor進行封裝。 讓我們用可以Executors取出來newFixedThreadPool(),newWorkStealingPool(),newSingleThreadExecutor(),newCachedThreadPool(),ScheduledExecutorService() 等等不同特點的線程池。
因此 ExecutorService=new ThreadPoolExecutor(…);
就可以創建出自己定製的線程池。
Executors內部也是這樣定製的。
主要實現如下
public class Executors {
/*
newFixedThreadPool 固定線程線程數量的線程池,該線程池內的線程數量始終不變,
如果任務到來,內部有空閒線程,則立即執行,如果沒有或任務數量大於線程數,多出來的任務,
則會被暫存到任務隊列中,待線程空閒,按先入先出的順序處理。
該任務隊列是LinkedBlockingQueue,是無界隊列,如果任務數量特別多,可能會導致內存不足
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
/*
newSingleThreadExecutor(),該方法返回一個只有一個線程的線程池,多出來的任務會被放到任務隊列內,
待線程空閒,按先入先出的順序執行隊列中的任務。隊列也是無界隊列
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
/*
newCachedThreadPool() ,該方法返回一個可以根據實際情況調整線程數量的線程池,線程數量不確定,初始是0,最大線程數是Integer.MAX_VALUE,如果有新的空閒線程可以複用,則會優先使用可複用的線程,如果所有線程都在工作,則創建新的線程,所有線程工作結束後,會返回線程池進行復用。但是如果超過60秒空閒,該線程會被銷燬。
該線程池慎用,在無限制的新增線程的場景下,很可能造成系統內存溢出
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
/*
newSingleThreadScheduledExecutor() 該方法返回一個線程池大小爲1,拓展了在給定時間執行某任務的功能,如在某個固定的延時之後執行,或者週期性執行某任務
*/
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
/*
返回同上,但是可以自己定義線程池大小
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
}
比較特別是newScheduledThreadPool ,這個是個計劃任務,會在指定的時間,對任務進行調度。
內部有兩個方法。
public ScheduledFuture<?> scheduleAtFixedRate(...)
和
public ScheduledFuture<?> scheduleWithFixedDelay(...)
第一個方法設置的定時任務,是從每個任務開始的時間+間隔 執行
第二個方法是 從每個任務結束的時間+間隔 執行
示例:
ScheduledExecutorService executorService= Executors.newScheduledThreadPool(10);
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
//打印當前系統秒數
System.out.println(System.currentTimeMillis()/1000);
}
},0,5,TimeUnit.SECONDS);
}
輸出如下:
1546681976
1546681981
1546681986
1546681991
1546681996
可以看到時間間隔是5秒。
但是請注意!如果任務執行時間 大於調度時間
比如說
ScheduledExecutorService executorService= Executors.newScheduledThreadPool(10);
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
//打印當前系統秒數
try {
//休眠3秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis()/1000);
}
},0,2,TimeUnit.SECONDS);
}
輸出結果:
1546682107
1546682110
1546682113
1546682116
1546682119
這裏任務的時間是3秒,但是調度時間間隔是2秒,結果就會變成定時任務3秒,也就是當任務時間大於間隔的時候,會在任務結束之後 立即調用下一次任務。並不會出現任務堆疊出現的情況。
如果使用的是scheduleWithFixedDelay(…) 方法,則是從任務結束開始調度,那麼時間將變成5秒間隔。
如果自己寫調度器,一定要注意異常的處理,一旦發生異常,所有的調度任務都會終止,所以爲了防止自己的調度器無疾而終,一定要有完善的異常處理機制