爲了更好的使用多線程,JDK提供了線程池供開發人員使用,目的在於減小線程的建立和銷燬次數,以此達到線程的重複利用。java
其中ThreadPoolExecutor是線程池中最核心的一個類,咱們先簡單看一下這個類的繼承關係。程序員
其中Executor是線程池的頂級接口,接口中只定義了一個方法 void execute(Runnable command);線程池的操做方法都是定義子在ExecutorService子接口中的,因此說ExecutorService是線程池真正的接口。多線程
ThreadPoolExecutor提供了四個構造方法,咱們看一下參數最全的一個構造函數;ide
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
}ThreadFactory threadFactory,
函數的參數含義以下:函數
本節咱們主要對前五個參數中的corePoolSize,maximumPoolSize及workQueue是如何配合使用作出說明(keepAliveTime,unit主要對空閒線程的存活時間作的定義,見名知意,再也不作出說明),以此來引出線程池的一些特性。spa
threadFactory和handler這兩個參數都有默認值,對於它們的用法將放到其它章節去作說明。線程
特性一:當池中正在運行的線程數(包括空閒線程)小於corePoolSize時,新建線程執行任務。code
下面用實驗來講明,代碼以下:blog
public class TestThreadPoolExecutor { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 3, 60L, TimeUnit.SECONDS,new LinkedBlockingQueue<>(1)); //任務1 pool.execute(new Runnable() { @Override public void run() { System.out.println("-------------helloworld_001---------------" + Thread.currentThread().getName()); } }); try { //主線程睡2秒 Thread.sleep(2*1000); } catch (InterruptedException e) { e.printStackTrace(); } //任務2 pool.execute(new Runnable() { @Override public void run() { System.out.println("-------------helloworld_002---------------" + Thread.currentThread().getName()); } }); } }
實驗結果以下:
繼承
實驗結果分析:
從實驗結果上能夠看出,當執行任務1的線程(thread-1)執行完成以後,任務2並無去複用thread-1而是新建線程(thread-2)去執行任務。
特性二:當池中正在運行的線程數大於等於corePoolSize時,新插入的任務進入workQueue排隊(若是workQueue長度容許),等待空閒線程來執行。
下面用實驗來講明,代碼以下:
public class TestThreadPoolExecutor { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1)); // 任務1 pool.execute(new Runnable() { @Override public void run() { try { Thread.sleep(3 * 1000); System.out.println("-------------helloworld_001---------------" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } }); // 任務2 pool.execute(new Runnable() { @Override public void run() { try { Thread.sleep(5 * 1000); System.out.println("-------------helloworld_002---------------" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } }); // 任務3 pool.execute(new Runnable() { @Override public void run() { System.out.println("-------------helloworld_003---------------" + Thread.currentThread().getName()); } }); } }
實驗結果以下:
實驗結果分析:
從實驗結果上看,任務3會等待任務1執行完以後,有了空閒線程,纔會執行。並無新建線程執行任務3,這時maximumPoolSize=3這個參數不起做用。
特性三:當隊列裏的任務數達到上限,而且池中正在運行的線程數小於maximumPoolSize,對於新加入的任務,新建線程。
下面用實驗來講明,代碼以下:
public class TestThreadPoolExecutor { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1)); // 任務1 pool.execute(new Runnable() { @Override public void run() { try { Thread.sleep(3 * 1000); System.out.println("-------------helloworld_001---------------" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } }); // 任務2 pool.execute(new Runnable() { @Override public void run() { try { Thread.sleep(5 * 1000); System.out.println("-------------helloworld_002---------------" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } }); // 任務3 pool.execute(new Runnable() { @Override public void run() { System.out.println("-------------helloworld_003---------------" + Thread.currentThread().getName()); } }); // 任務4 pool.execute(new Runnable() { @Override public void run() { System.out.println("-------------helloworld_004---------------" + Thread.currentThread().getName()); } }); } }
實驗結果以下:
實驗結果分析:
當任務4進入隊列時發現隊列的長度已經到了上限,因此沒法進入隊列排隊,而此時正在運行的線程數(2)小於maximumPoolSize因此新建線程執行該任務。
特性四:當隊列裏的任務數達到上限,而且池中正在運行的線程數等於maximumPoolSize,對於新加入的任務,執行拒絕策略(線程池默認的拒絕策略是拋異常)。
下面用實驗來講明,代碼以下:
public class TestThreadPoolExecutor { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1)); // 任務1 pool.execute(new Runnable() { @Override public void run() { try { Thread.sleep(3 * 1000); System.out.println("-------------helloworld_001---------------" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } }); // 任務2 pool.execute(new Runnable() { @Override public void run() { try { Thread.sleep(5 * 1000); System.out.println("-------------helloworld_002---------------" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } }); // 任務3 pool.execute(new Runnable() { @Override public void run() { System.out.println("-------------helloworld_003---------------" + Thread.currentThread().getName()); } }); // 任務4 pool.execute(new Runnable() { @Override public void run() { try { Thread.sleep(2 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-------------helloworld_004---------------" + Thread.currentThread().getName()); } }); // 任務5 pool.execute(new Runnable() { @Override public void run() { System.out.println("-------------helloworld_005---------------" + Thread.currentThread().getName()); } }); } }
實驗結果以下:
實驗結果分析:
當任務5加入時,隊列達到上限,池內運行的線程數達到最大,故執行默認的拒絕策略,拋異常。
本文中使用到的隊列類型雖然僅限於LinkedBlockingQueue這一種隊列類型,但總結出來的特性,對與經常使用ArrayBlockingQueue 和 SynchronousQueue一樣適用,些許不一樣及三種隊列的區別,將在下個章節中說明。
最後說一點,咱們做爲程序員,研究問題仍是要仔細深刻一點的。當你對原理了解的有夠透徹,開發起來也就駕輕就熟了,不少開發中的問題和疑惑也就迎刃而解了,並且在面對其餘問題的時候也可作到舉一反三。固然在開發中沒有太多的時間讓你去研究原理,開發中要以實現功能爲前提,可等項目上線的後,你有大把的時間或者空餘的時間,你大可去刨根問底,深刻的去研究一項技術,爲以爲這對一名程序員的成長是很重要的事情。