11.執行緒的狀態,執行緒池的構造方式,執行緒池的特性
一:執行緒的狀態
(1)初始狀態
實現Runnable介面和繼承Thread可以得到一個執行緒類,new一個例項出來,執行緒就進入了初始狀態。
(2) 就緒狀態
1)就緒狀態只是說你資格執行,排程程式沒有挑選到你,你就永遠是就緒狀態。
2)呼叫執行緒的start()方法,此執行緒進入就緒狀態。
3)當前執行緒sleep()方法結束,其他執行緒join()結束,等待使用者輸入完畢,某個執行緒拿到物件鎖,這些執行緒也將進入就緒狀態。
4)當前執行緒時間片用完了,呼叫當前執行緒的yield()方法,當前執行緒進入就緒狀態。
5)鎖池裡的執行緒拿到物件鎖後,進入就緒狀態。
(3)執行中狀態
執行緒排程程式從可執行池中選擇一個執行緒作為當前執行緒時執行緒所處的狀態。這也是執行緒進入執行狀態的唯一一種方式。
(4)阻塞狀態
阻塞狀態是執行緒阻塞在進入synchronized關鍵字修飾的方法或程式碼塊(獲取鎖)時的狀態。
(5)等待
處於這種狀態的執行緒不會被分配CPU執行時間,它們要等待被顯式地喚醒,否則會處於無限期等待的狀態。
(6) 超時等待
處於這種狀態的執行緒不會被分配CPU執行時間,不過無須無限期等待被其他執行緒顯示地喚醒,在達到一定時間後它們會自動喚醒。
(7)終止狀態
1)當執行緒的run()方法完成時,或者主執行緒的main()方法完成時,我們就認為它終止了。這個執行緒物件也許是活的,但是,它已經不是一個單獨執行的執行緒。執行緒一旦終止了,就不能復生。
2)在一個終止的執行緒上呼叫start()方法,會丟擲java.lang.IllegalThreadStateException異常。
二:執行緒池的構造方式
ThreadPoolExecutor的建構函式:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
}
函式的引數含義如下:
corePoolSize: 執行緒池核心執行緒數
maximumPoolSize:執行緒池最大數
keepAliveTime: 空閒執行緒存活時間
unit: 時間單位
workQueue: 執行緒池所使用的緩衝佇列
threadFactory:執行緒池建立執行緒使用的工廠
handler: 執行緒池對拒絕任務的處理策略
三:執行緒池的特性
(1):當池中正在執行的執行緒數(包括空閒執行緒)小於corePoolSize時,新建執行緒執行任務。
下面用實驗來說明,程式碼如下:
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)去執行任務。
(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這個引數不起作用。
(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所以新建執行緒執行該任務。
(4):當佇列裡的任務數達到上限,並且池中正在執行的執行緒數等於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同樣適用。