Java中的線程池是運用場景最多的併發框架,幾乎全部須要異步或併發執行任務的程序 均可以使用線程池。在開發過程當中,合理地使用線程池可以帶來3個好處。java
頻繁的建立多線程,很是佔用CPU,線程過多時形成線程池溢出數據庫
線程池是爲忽然大量爆發的線程設計的,經過有限的幾個固定線程爲大量的操做服務,減小了建立和銷燬線程所需的時間,從而提升效率。緩存
若是一個線程的時間很是長,就不必用線程池了(不是不能做長時間操做,而是不宜。),何況咱們還不能控制線程池中線程的開始、掛起、和停止。多線程
Executor框架的最頂層實現是ThreadPoolExecutor類,Executors工廠類中提供的newScheduledThreadPool、newFixedThreadPool、newCachedThreadPool方法其實也只是ThreadPoolExecutor的構造函數參數不一樣而已。經過傳入不一樣的參數,就能夠構造出適用於不一樣應用場景下的線程池。併發
Java經過Executors(jdk1.5併發包)提供四種線程池,分別爲:框架
建立一個可緩存線程池,若是線程池長度超過處理須要,可靈活回收空閒線程,若無可回收,則新建線程。示例代碼以下:異步
// 無限大小線程池 jvm自動回收
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int temp = i;
newCachedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName() + ",i:" + temp);
}
});
}
複製代碼
總結: 線程池爲無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次新建線程。jvm
建立一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。示例代碼以下:ide
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
final int temp = i;
newFixedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getId() + ",i:" + temp);
}
});
}
複製代碼
總結:由於線程池大小爲5,每一個任務輸出index後sleep 2秒,因此每兩秒打印3個數字。 定長線程池的大小最好根據系統資源進行設置。如Runtime.getRuntime().availableProcessors()函數
建立一個定長線程池,支持定時及週期性任務執行。延遲執行示例代碼以下:
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 10; i++) {
final int temp = i;
newScheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println("i:" + temp);
}
}, 3, TimeUnit.SECONDS);
}
複製代碼
程序啓動後會等待3秒,再執行
建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務,保證全部任務按照指定順序(FIFO, LIFO, 優先級)執行。示例代碼以下:
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
newSingleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("index:" + index);
try {
Thread.sleep(200);
} catch (Exception e) {
// TODO: handle exception
}
}
});
}
複製代碼
結果依次輸出
提交一個任務到線程池中,線程池的處理流程以下:
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, new ArrayBlockingQueue<>(3))
new ThreadPoolExecutor(1, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3))
複製代碼
若是當前線程池中的線程數目小於corePoolSize,則每來一個任務,就會建立一個線程去執行這個任務;
若是當前線程池中的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閒線程將其取出去執行;若添加失敗(通常來講是任務緩存隊列已滿),則會嘗試建立新的線程去執行這個任務;
若是隊列已經滿了,則在總線程數不大於maximumPoolSize的前提下,則建立新的線程
若是當前線程池中的線程數目達到maximumPoolSize,則會採起任務拒絕策略進行處理;
若是線程池中的線程數量大於 corePoolSize時,若是某線程空閒時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;
若是容許爲核心池中的線程設置存活時間,那麼核心池中的線程空閒時間超過keepAliveTime,線程也會被終止。
public class Test0007 {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3));
for (int i = 1; i <= 6; i++) {
TaskThred t1 = new TaskThred("任務" + i);
executor.execute(t1);
}
executor.shutdown();
}
}
class TaskThred implements Runnable {
private String taskName;
public TaskThred(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+taskName);
}
}
複製代碼
CPU密集的意思是該任務須要大量的運算,而沒有阻塞,CPU一直全速運行。 CPU密集任務只有在真正的多核CPU上纔可能獲得加速(經過多線程),而在單核CPU上,不管你開幾個模擬的多線程,該任務都不可能獲得加速,由於CPU總的運算能力就那些。
IO密集型,即該任務須要大量的IO,即大量的阻塞。在單線程上運行IO密集型的任務會致使浪費大量的CPU運算能力浪費在等待。因此在IO密集型任務中使用多線程能夠大大的加速程序運行,即時在單核CPU上,這種加速主要就是利用了被浪費掉的阻塞時間。
要想合理的配置線程池的大小,首先得分析任務的特性,能夠從如下幾個角度分析:
性質不一樣的任務能夠交給不一樣規模的線程池執行。
對於不一樣性質的任務來講,CPU密集型任務應配置儘量小的線程,如配置CPU個數+1的線程數,IO密集型任務應配置儘量多的線程,由於IO操做不佔用CPU,不要讓CPU閒下來,應加大線程數量,如配置兩倍CPU個數+1,而對於混合型的任務,若是能夠拆分,拆分紅IO密集型和CPU密集型分別處理,前提是二者運行的時間是差很少的,若是處理時間相差很大,則不必拆分了。
若任務對其餘系統資源有依賴,如某個任務依賴數據庫的鏈接返回的結果,這時候等待的時間越長,則CPU空閒的時間越長,那麼線程數量應設置得越大,才能更好的利用CPU。
固然具體合理線程池值大小,須要結合系統實際狀況,在大量的嘗試下比較才能得出
我的博客 蝸牛