您好,登錄后才能下訂單哦!
本文實例講述了JAVA線程池原理。分享給大家供大家參考,具體如下:
線程池的優點
1、線程是稀缺資源,使用線程池可以減少創建和銷毀線程的次數,每個工作線程都可以重復使用。
2、可以根據系統的承受能力,調整線程池中工作線程的數量,防止因為消耗過多內存導致服務器崩潰。
線程池的創建
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
線程池的實現原理
提交一個任務到線程池中,線程池的處理流程如下:
1、判斷線程池里的核心線程是否都在執行任務,如果不是(核心線程空閑或者還有核心線程沒有被創建)則創建一個新的工作線程來執行任務。如果核心線程都在執行任務,則進入下個流程。
2、線程池判斷工作隊列是否已滿,如果工作隊列沒有滿,則將新提交的任務存儲在這個工作隊列里。如果工作隊列滿了,則進入下個流程。
3、判斷線程池里的線程是否都處于工作狀態,如果沒有,則創建一個新的工作線程來執行任務。如果已經滿了,則交給飽和策略來處理這個任務。
線程池的源碼解讀
1、ThreadPoolExecutor的execute()
方法
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); //如果線程數大于等于基本線程數或者線程創建失敗,將任務加入隊列 if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {//線程池處于運行狀態并且加入隊列成功 if (runState == RUNNING && workQueue.offer(command)) { if (runState != RUNNING || poolSize == 0) ensureQueuedTaskHandled(command); }//線程池不處于運行狀態或者加入隊列失敗,則創建線程(創建的是非核心線程) else if (!addIfUnderMaximumPoolSize(command))//創建線程失敗,則采取阻塞處理的方式 reject(command); // is shutdown or saturated } }
2、創建線程的方法:addIfUnderCorePoolSize(command)
private boolean addIfUnderCorePoolSize(Runnable firstTask) { Thread t = null; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (poolSize < corePoolSize && runState == RUNNING) t = addThread(firstTask); } finally { mainLock.unlock(); } if (t == null) return false; t.start(); return true; }
我們重點來看第7行:
private Thread addThread(Runnable firstTask) { Worker w = new Worker(firstTask); Thread t = threadFactory.newThread(w); if (t != null) { w.thread = t; workers.add(w); int nt = ++poolSize; if (nt > largestPoolSize) largestPoolSize = nt; } return t; }
這里將線程封裝成工作線程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 { @Override public void run() { try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } }
2、線程池循環運行16個線程:
public static void main(String[] args) { LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(5); ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, queue); for (int i = 0; i < 16 ; i++) { threadPool.execute( new Thread(new ThreadPoolTest(), "Thread".concat(i + ""))); System.out.println("線程池中活躍的線程數: " + threadPool.getPoolSize()); if (queue.size() > 0) { System.out.println("----------------隊列中阻塞的線程數" + queue.size()); } } threadPool.shutdown(); }
執行結果:
線程池中活躍的線程數: 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)
從結果可以觀察出:
1、創建的線程池具體配置為:核心線程數量為5個;全部線程數量為10個;工作隊列的長度為5。
2、我們通過queue.size()
的方法來獲取工作隊列中的任務數。
3、運行原理:
剛開始都是在創建新的線程,達到核心線程數量5個后,新的任務進來后不再創建新的線程,而是將任務加入工作隊列,任務隊列到達上線5個后,新的任務又會創建新的普通線程,直到達到線程池最大的線程數量10個,后面的任務則根據配置的飽和策略來處理。我們這里沒有具體配置,使用的是默認的配置AbortPolicy:直接拋出異常。
當然,為了達到我需要的效果,上述線程處理的任務都是利用休眠導致線程沒有釋放!!!
RejectedExecutionHandler:飽和策略
當隊列和線程池都滿了,說明線程池處于飽和狀態,那么必須對新提交的任務采用一種特殊的策略來進行處理。這個策略默認配置是AbortPolicy,表示無法處理新的任務而拋出異常。JAVA提供了4中策略:
1、AbortPolicy:直接拋出異常
2、CallerRunsPolicy:只用調用所在的線程運行任務
3、DiscardOldestPolicy:丟棄隊列里最近的一個任務,并執行當前任務。
4、DiscardPolicy:不處理,丟棄掉。
我們現在用第四種策略來處理上面的程序:
public static void main(String[] args) { LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(3); RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy(); ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue,handler); for (int i = 0; i < 9 ; i++) { threadPool.execute( new Thread(new ThreadPoolTest(), "Thread".concat(i + ""))); System.out.println("線程池中活躍的線程數: " + threadPool.getPoolSize()); if (queue.size() > 0) { System.out.println("----------------隊列中阻塞的線程數" + queue.size()); } } threadPool.shutdown(); }
執行結果:
線程池中活躍的線程數: 1
線程池中活躍的線程數: 2
線程池中活躍的線程數: 2
----------------隊列中阻塞的線程數1
線程池中活躍的線程數: 2
----------------隊列中阻塞的線程數2
線程池中活躍的線程數: 2
----------------隊列中阻塞的線程數3
線程池中活躍的線程數: 3
----------------隊列中阻塞的線程數3
線程池中活躍的線程數: 4
----------------隊列中阻塞的線程數3
線程池中活躍的線程數: 5
----------------隊列中阻塞的線程數3
線程池中活躍的線程數: 5
----------------隊列中阻塞的線程數3
這里采用了丟棄策略后,就沒有再拋出異常,而是直接丟棄。在某些重要的場景下,可以采用記錄日志或者存儲到數據庫中,而不應該直接丟棄。
設置策略有兩種方式:
1、
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy(); ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue,handler);
2、
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue); threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
更多java相關內容感興趣的讀者可查看本站專題:《Java進程與線程操作技巧總結》、《Java數據結構與算法教程》、《Java操作DOM節點技巧總結》、《Java文件與目錄操作技巧匯總》和《Java緩存操作技巧匯總》
希望本文所述對大家java程序設計有所幫助。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。