一、線程是稀缺資源,使用線程池能夠減小建立和銷燬線程的次數,每一個工做線程均可以重複使用。java
二、能夠根據系統的承受能力,調整線程池中工做線程的數量,防止由於消耗過多內存致使服務器崩潰。數據庫
1 public ThreadPoolExecutor(int corePoolSize, 2 int maximumPoolSize, 3 long keepAliveTime, 4 TimeUnit unit, 5 BlockingQueue<Runnable> workQueue, 6 RejectedExecutionHandler handler)
corePoolSize:線程池核心線程數量服務器
maximumPoolSize:線程池最大線程數量ide
keepAliverTime:當活躍線程數大於核心線程數時,空閒的多餘線程最大存活時間this
unit:存活時間的單位spa
workQueue:存聽任務的隊列線程
handler:超出線程範圍和隊列容量的任務的處理程序日誌
提交一個任務到線程池中,線程池的處理流程以下:code
一、判斷線程池裏的核心線程是否都在執行任務,若是不是(核心線程空閒或者還有核心線程沒有被建立)則建立一個新的工做線程來執行任務。若是核心線程都在執行任務,則進入下個流程。blog
二、線程池判斷工做隊列是否已滿,若是工做隊列沒有滿,則將新提交的任務存儲在這個工做隊列裏。若是工做隊列滿了,則進入下個流程。
三、判斷線程池裏的線程是否都處於工做狀態,若是沒有,則建立一個新的工做線程來執行任務。若是已經滿了,則交給飽和策略來處理這個任務。
一、ThreadPoolExecutor的execute()方法
1 public void execute(Runnable command) { 2 if (command == null) 3 throw new NullPointerException();
//若是線程數大於等於基本線程數或者線程建立失敗,將任務加入隊列 4 if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
//線程池處於運行狀態而且加入隊列成功 5 if (runState == RUNNING && workQueue.offer(command)) { 6 if (runState != RUNNING || poolSize == 0) 7 ensureQueuedTaskHandled(command); 8 }
//線程池不處於運行狀態或者加入隊列失敗,則建立線程(建立的是非核心線程) 9 else if (!addIfUnderMaximumPoolSize(command))
//建立線程失敗,則採起阻塞處理的方式 10 reject(command); // is shutdown or saturated 11 } 12 }
二、建立線程的方法:addIfUnderCorePoolSize(command)
1 private boolean addIfUnderCorePoolSize(Runnable firstTask) { 2 Thread t = null; 3 final ReentrantLock mainLock = this.mainLock; 4 mainLock.lock(); 5 try { 6 if (poolSize < corePoolSize && runState == RUNNING) 7 t = addThread(firstTask); 8 } finally { 9 mainLock.unlock(); 10 } 11 if (t == null) 12 return false; 13 t.start(); 14 return true; 15 }
咱們重點來看第7行:
1 private Thread addThread(Runnable firstTask) { 2 Worker w = new Worker(firstTask); 3 Thread t = threadFactory.newThread(w); 4 if (t != null) { 5 w.thread = t; 6 workers.add(w); 7 int nt = ++poolSize; 8 if (nt > largestPoolSize) 9 largestPoolSize = nt; 10 } 11 return t; 12 }
這裏將線程封裝成工做線程worker,並放入工做線程組裏,worker類的方法run方法:
public void run() { try { Runnable task = firstTask; firstTask = null; while (task != null || (task = getTask()) != null) { runTask(task); task = null; } } finally { workerDone(this); } }
worker在執行完任務後,還會經過getTask方法循環獲取工做隊裏裏的任務來執行。
咱們經過一個程序來觀察線程池的工做原理:
一、建立一個線程
1 public class ThreadPoolTest implements Runnable 2 { 3 @Override 4 public void run() 5 { 6 try 7 { 8 Thread.sleep(300); 9 } 10 catch (InterruptedException e) 11 { 12 e.printStackTrace(); 13 } 14 } 15 }
二、線程池循環運行16個線程:
1 public static void main(String[] args) 2 { 3 LinkedBlockingQueue<Runnable> queue = 4 new LinkedBlockingQueue<Runnable>(5); 5 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, queue); 6 for (int i = 0; i < 16 ; i++) 7 { 8 threadPool.execute( 9 new Thread(new ThreadPoolTest(), "Thread".concat(i + ""))); 10 System.out.println("線程池中活躍的線程數: " + threadPool.getPoolSize()); 11 if (queue.size() > 0) 12 { 13 System.out.println("----------------隊列中阻塞的線程數" + queue.size()); 14 } 15 } 16 threadPool.shutdown(); 17 }
執行結果:
線程池中活躍的線程數: 1 線程池中活躍的線程數: 2 線程池中活躍的線程數: 3 線程池中活躍的線程數: 4 線程池中活躍的線程數: 5 線程池中活躍的線程數: 5 ----------------隊列中阻塞的線程數1 線程池中活躍的線程數: 5 ----------------隊列中阻塞的線程數2 線程池中活躍的線程數: 5 ----------------隊列中阻塞的線程數3 線程池中活躍的線程數: 5 ----------------隊列中阻塞的線程數4 線程池中活躍的線程數: 5 ----------------隊列中阻塞的線程數5 線程池中活躍的線程數: 6 ----------------隊列中阻塞的線程數5 線程池中活躍的線程數: 7 ----------------隊列中阻塞的線程數5 線程池中活躍的線程數: 8 ----------------隊列中阻塞的線程數5 線程池中活躍的線程數: 9 ----------------隊列中阻塞的線程數5 線程池中活躍的線程數: 10 ----------------隊列中阻塞的線程數5 Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task Thread[Thread15,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@232204a1[Running, pool size = 10, active threads = 10, queued tasks = 5, completed tasks = 0] at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047) at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369) at test.ThreadTest.main(ThreadTest.java:17)
從結果能夠觀察出:
一、建立的線程池具體配置爲:核心線程數量爲5個;所有線程數量爲10個;工做隊列的長度爲5。
二、咱們經過queue.size()的方法來獲取工做隊列中的任務數。
三、運行原理:
剛開始都是在建立新的線程,達到核心線程數量5個後,新的任務進來後再也不建立新的線程,而是將任務加入工做隊列,任務隊列到達上線5個後,新的任務又會建立新的普通線程,直到達到線程池最大的線程數量10個,後面的任務則根據配置的飽和策略來處理。咱們這裏沒有具體配置,使用的是默認的配置AbortPolicy:直接拋出異常。
固然,爲了達到我須要的效果,上述線程處理的任務都是利用休眠致使線程沒有釋放!!!
當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須對新提交的任務採用一種特殊的策略來進行處理。這個策略默認配置是AbortPolicy,表示沒法處理新的任務而拋出異常。JAVA提供了4中策略:
一、AbortPolicy:直接拋出異常
二、CallerRunsPolicy:只用調用所在的線程運行任務
三、DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。
四、DiscardPolicy:不處理,丟棄掉。
咱們如今用第四種策略來處理上面的程序:
1 public static void main(String[] args) 2 { 3 LinkedBlockingQueue<Runnable> queue = 4 new LinkedBlockingQueue<Runnable>(3); 5 RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy(); 6 7 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue,handler); 8 for (int i = 0; i < 9 ; i++) 9 { 10 threadPool.execute( 11 new Thread(new ThreadPoolTest(), "Thread".concat(i + ""))); 12 System.out.println("線程池中活躍的線程數: " + threadPool.getPoolSize()); 13 if (queue.size() > 0) 14 { 15 System.out.println("----------------隊列中阻塞的線程數" + queue.size()); 16 } 17 } 18 threadPool.shutdown(); 19 }
執行結果:
線程池中活躍的線程數: 1 線程池中活躍的線程數: 2 線程池中活躍的線程數: 2 ----------------隊列中阻塞的線程數1 線程池中活躍的線程數: 2 ----------------隊列中阻塞的線程數2 線程池中活躍的線程數: 2 ----------------隊列中阻塞的線程數3 線程池中活躍的線程數: 3 ----------------隊列中阻塞的線程數3 線程池中活躍的線程數: 4 ----------------隊列中阻塞的線程數3 線程池中活躍的線程數: 5 ----------------隊列中阻塞的線程數3 線程池中活躍的線程數: 5 ----------------隊列中阻塞的線程數3
這裏採用了丟棄策略後,就沒有再拋出異常,而是直接丟棄。在某些重要的場景下,能夠採用記錄日誌或者存儲到數據庫中,而不該該直接丟棄。
設置策略有兩種方式:
一、
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy(); ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue,handler);
二、
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue); threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());