1.引言
合理利用線程池能夠帶來三個好處。第一:降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。第二:提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。第三:提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。但是要做到合理的利用線程池,必須對其原理了如指掌。
2.線程池使用
Executors提供的四種線程 1.newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。 2.newFixedThreadPool 創建一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。 3.newScheduledThreadPool 創建一個定長線程池,支持定時及周期性任務執行。 4.newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
1.newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。示例如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
ExecutorService executorService = Executors.newCachedThreadPool(); for ( int i= 0 ;i< 5 ;i++){ final int index = i; try { Thread.sleep(index * 1000 ); } catch (InterruptedException e) { e.printStackTrace(); } executorService.execute( new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "," +index); } }); } //控制臺信息 pool- 1 -thread- 1 , 0 pool- 1 -thread- 1 , 1 pool- 1 -thread- 1 , 2 pool- 1 -thread- 1 , 3 pool- 1 -thread- 1 , 4 |
2.newFixedThreadPool創建一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。示例如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
ExecutorService fixedThreadPool = Executors.newFixedThreadPool( 4 ); for ( int i= 0 ;i< 5 ;i++) { final int index = i; fixedThreadPool.execute( new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + ", " + index); Thread.sleep( 2000 ); } catch (InterruptedException e) { e.printStackTrace(); } } }); } //控制臺信息 pool- 1 -thread- 1 , 0 pool- 1 -thread- 2 , 1 pool- 1 -thread- 3 , 2 pool- 1 -thread- 4 , 3 pool- 1 -thread- 1 , 4 |
3.newScheduledThreadPool 創建一個定長線程池,支持周期和定時任務示例如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool( 5 ); System.out.println( "before:" + System.currentTimeMillis()/ 1000 ); scheduledThreadPool.schedule( new Runnable() { @Override public void run() { System.out.println( "延遲3秒執行的哦 :" + System.currentTimeMillis()/ 1000 ); } }, 3 , TimeUnit.SECONDS); System.out.println( "after :" +System.currentTimeMillis()/ 1000 ); //控制臺信息 before: 1518012703 after : 1518012703 延遲 3 秒執行的哦 : 1518012706 System.out.println( "before:" + System.currentTimeMillis()/ 1000 ); scheduledThreadPool.scheduleAtFixedRate( new Runnable() { @Override public void run() { System.out.println( "延遲1秒之后,3秒執行一次:" +System.currentTimeMillis()/ 1000 ); } }, 1 , 3 , TimeUnit.SECONDS); System.out.println( "after :" +System.currentTimeMillis()/ 1000 ); |
控制臺消息
before:1518013024
after :1518013024
延遲1秒之后,3秒執行一次:1518013025
延遲1秒之后,3秒執行一次:1518013028
延遲1秒之后,3秒執行一次:1518013031
4.newSingleThreadExecutor創建一個單線程化的線程池,只會用工作線程來執行任務,保證順序,示例如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); for ( int i= 0 ;i< 10 ;i++) { final int index = i; singleThreadExecutor.execute( new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + "," + index); Thread.sleep( 2000 ); } catch (InterruptedException e) { e.printStackTrace(); } } }); } |
控制臺信息
pool-1-thread-1,0
pool-1-thread-1,1
pool-1-thread-1,2
pool-1-thread-1,3
pool-1-thread-1,4
向線程池提交任務 ThreadPoolExecutor類中execute()和submit()區別 execute()方法實際上是Executor中聲明的方法,在ThreadPoolExecutor進行了具體的實現,這個方法是ThreadPoolExecutor的核心方法,通過這個方法可以向線程池提交一個任務,交由線程池去執行。
submit()方法是在ExecutorService中聲明的方法,在AbstractExecutorService就已經有了具體的實現,在ThreadPoolExecutor中并沒有對其進行重寫,這個方法也是用來向線程池提交任務的,但是它和execute()方法不同,它能夠返回任務執行的結果,通過源碼查看submit()方法的實現,會發現它實際上還是調用的execute()方法,只不過它利用了Future來獲取任務執行結果。
1
2
3
4
5
6
7
8
9
10
|
/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public Future<?> submit(Runnable task) { if (task == null ) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null ); execute(ftask); return ftask; } |
線程池的關閉 我們可以通過調用線程池的shutdown或shutdownNow方法來關閉線程池,但是它們的實現原理不同,shutdown的原理是只是將線程池的狀態設置成SHUTDOWN狀態,然后中斷所有沒有正在執行任務的線程。shutdownNow的原理是遍歷線程池中的工作線程,然后逐個調用線程的interrupt方法來中斷線程,所以無法響應中斷的任務可能永遠無法終止。shutdownNow會首先將線程池的狀態設置成STOP,然后嘗試停止所有的正在執行或暫停任務的線程,并返回等待執行任務的列表。
只要調用了這兩個關閉方法的其中一個,isShutdown方法就會返回true。當所有的任務都已關閉后,才表示線程池關閉成功,這時調用isTerminaed方法會返回true。至于我們應該調用哪一種方法來關閉線程池,應該由提交到線程池的任務特性決定,通常調用shutdown來關閉線程池,如果任務不一定要執行完,則可以調用shutdownNow。
3. 線程池的分析
流程分析:線程池的主要工作流程如下圖: Java線程池主要工作流程
從上圖我們可以看出,當提交一個新任務到線程池時,線程池的處理流程如下:
- 首先線程池判斷基本線程池是否已滿?沒滿,創建一個工作線程來執行任務。滿了,則進入下個流程。
- 其次線程池判斷工作隊列是否已滿?沒滿,則將新提交的任務存儲在工作隊列里。滿了,則進入下個流程。
- 最后線程池判斷整個線程池是否已滿?沒滿,則創建一個新的工作線程來執行任務,滿了,則交給飽和策略來處理這個任務。
**源碼分析。**上面的流程分析讓我們很直觀的了解的線程池的工作原理,讓我們再通過源代碼來看看是如何實現的。線程池執行任務的方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public void execute(Runnable command) { if (command == null ) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true )) return ; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0 ) addWorker( null , false ); } else if (!addWorker(command, false )) reject(command); } |
工作線程。線程池創建線程時,會將線程封裝成工作線程Worker,Worker在執行完任務后,還會無限循環獲取工作隊列里的任務來執行。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://my.oschina.net/u/3758413/blog/1620433