阿里p7一枚,能夠關注公衆號:路人甲Java,我們一塊兒同行!java
這是java高併發系列第18篇文章。數據庫
你們用jdbc操做過數據庫應該知道,操做數據庫須要和數據庫創建鏈接,拿到鏈接以後才能操做數據庫,用完以後銷燬。數據庫鏈接的建立和銷燬實際上是比較耗時的,真正和業務相關的操做耗時是比較短的。每一個數據庫操做以前都須要建立鏈接,爲了提高系統性能,後來出現了數據庫鏈接池,系統啓動的時候,先建立不少鏈接放在池子裏面,使用的時候,直接從鏈接池中獲取一個,使用完畢以後返回到池子裏面,繼續給其餘須要者使用,這其中就省去建立鏈接的時間,從而提高了系統總體的性能。數組
線程池和數據庫鏈接池的原理也差很少,建立線程去處理業務,可能建立線程的時間比處理業務的時間還長一些,若是系統可以提早爲咱們建立好線程,咱們須要的時候直接拿來使用,用完以後不是直接將其關閉,而是將其返回到線程中中,給其餘須要這使用,這樣直接節省了建立和銷燬的時間,提高了系統的性能。緩存
簡單的說,在使用了線程池以後,建立線程變成了從線程池中獲取一個空閒的線程,而後使用,關閉線程變成了將線程歸還到線程池。安全
當向線程池提交一個任務以後,線程池的處理流程以下:微信
流程以下圖:多線程
舉個例子,加深理解:併發
我們做爲開發者,上面都有開發主管,主管下面帶領幾個小弟幹活,CTO給主管受權說,你能夠招聘5個小弟幹活,新來任務,若是小弟還不到吳哥,當即去招聘一個來幹這個新來的任務,當5個小弟都招來了,再來任務以後,將任務記錄到一個表格中,表格中最多記錄100個,小弟們會主動去表格中獲取任務執行,若是5個小弟都在幹活,而且表格中也記錄滿了,那你能夠將小弟擴充到20個,若是20個小弟都在幹活,而且存聽任務的表也滿了,產品經理再來任務後,是直接拒絕,仍是讓產品本身幹,這個由你本身決定,小弟們都全力以赴在幹活,任務都被處理完了,忽然公司業績下滑,幾個員工沒事幹,打醬油,爲了節約成本,CTO主管把小弟控制到5人,其餘15我的直接被幹掉了。因此做爲小弟們,別讓本身閒着,多幹活。框架
原理:先找幾我的幹活,你們都忙於幹活,任務太多能夠排期,排期的任務太多了,再招一些人來幹活,最後幹活的和排期都達到上層領導要求的上限了,那須要採起一些其餘策略進行處理了。對於長時間不幹活的人,考慮將其開掉,節約資源和成本。ide
jdk中提供了線程池的具體實現,實現類是:java.util.concurrent.ThreadPoolExecutor
,主要構造方法:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize:核心線程大小,當提交一個任務到線程池時,線程池會建立一個線程來執行任務,即便有其餘空閒線程能夠處理任務也會創新線程,等到工做的線程數大於核心線程數時就不會在建立了。若是調用了線程池的prestartAllCoreThreads
方法,線程池會提早把核心線程都創造好,並啓動
maximumPoolSize:線程池容許建立的最大線程數。若是隊列滿了,而且以建立的線程數小於最大線程數,則線程池會再建立新的線程執行任務。若是咱們使用了無界隊列,那麼全部的任務會加入隊列,這個參數就沒有什麼效果了
keepAliveTime:線程池的工做線程空閒後,保持存活的時間。若是沒有任務處理了,有些線程會空閒,空閒的時間超過了這個值,會被回收掉。若是任務不少,而且每一個任務的執行時間比較短,避免線程重複建立和回收,能夠調大這個時間,提升線程的利用率
unit:keepAliveTIme的時間單位,能夠選擇的單位有天、小時、分鐘、毫秒、微妙、千分之一毫秒和納秒。類型是一個枚舉java.util.concurrent.TimeUnit
,這個枚舉也常用,有興趣的能夠看一下其源碼
workQueue:工做隊列,用於緩存待處理任務的阻塞隊列,常見的有4種,本文後面有介紹
threadFactory:線程池中建立線程的工廠,能夠經過線程工廠給每一個建立出來的線程設置更有意義的名字
handler:飽和策略,當線程池沒法處理新來的任務了,那麼須要提供一種策略處理提交的新任務,默認有4種策略,文章後面會提到
調用線程池的execute方法處理任務,執行execute方法的過程:
maximumPoolSize
,是:則新增線程處理當前傳入的任務,否:將任務傳遞給handler
對象rejectedExecution
方法處理線程池的使用步驟:
調用構造方法建立線程池
關閉線程池
上一個簡單的示例,以下:
package com.itsoku.chat16; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 跟着阿里p7學併發,微信公衆號:javacode2018 */ public class Demo1 { static ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 5, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); public static void main(String[] args) { for (int i = 0; i < 10; i++) { int j = i; String taskName = "任務" + j; executor.execute(() -> { //模擬任務內部處理耗時 try { TimeUnit.SECONDS.sleep(j); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + taskName + "處理完畢"); }); } //關閉線程池 executor.shutdown(); } }
輸出:
pool-1-thread-1任務0處理完畢 pool-1-thread-2任務1處理完畢 pool-1-thread-3任務2處理完畢 pool-1-thread-1任務3處理完畢 pool-1-thread-2任務4處理完畢 pool-1-thread-3任務5處理完畢 pool-1-thread-1任務6處理完畢 pool-1-thread-2任務7處理完畢 pool-1-thread-3任務8處理完畢 pool-1-thread-1任務9處理完畢
任務太多的時候,工做隊列用於暫時緩存待處理的任務,jdk中常見的5種阻塞隊列:
ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按照先進先出原則對元素進行排序
LinkedBlockingQueue:是一個基於鏈表結構的阻塞隊列,此隊列按照先進先出排序元素,吞吐量一般要高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool
使用了這個隊列。
SynchronousQueue :一個不存儲元素的阻塞隊列,每一個插入操做必須等到另一個線程調用移除操做,不然插入操做一直處理阻塞狀態,吞吐量一般要高於LinkedBlockingQueue,靜態工廠方法Executors.newCachedThreadPool
使用這個隊列
PriorityBlockingQueue:優先級隊列,進入隊列的元素按照優先級會進行排序
前2種隊列相關示例就不說了,主要說一下後面2種隊列的使用示例。
package com.itsoku.chat16; import java.util.concurrent.*; /** * 跟着阿里p7學併發,微信公衆號:javacode2018 */ public class Demo2 { public static void main(String[] args) { ExecutorService executor = Executors.newCachedThreadPool(); for (int i = 0; i < 50; i++) { int j = i; String taskName = "任務" + j; executor.execute(() -> { System.out.println(Thread.currentThread().getName() + "處理" + taskName); //模擬任務內部處理耗時 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } }); } executor.shutdown(); } }
pool-1-thread-1處理任務0 pool-1-thread-2處理任務1 pool-1-thread-3處理任務2 pool-1-thread-6處理任務5 pool-1-thread-7處理任務6 pool-1-thread-4處理任務3 pool-1-thread-5處理任務4 pool-1-thread-8處理任務7 pool-1-thread-9處理任務8 pool-1-thread-10處理任務9 pool-1-thread-11處理任務10 pool-1-thread-12處理任務11 pool-1-thread-13處理任務12 pool-1-thread-14處理任務13 pool-1-thread-15處理任務14 pool-1-thread-16處理任務15 pool-1-thread-17處理任務16 pool-1-thread-18處理任務17 pool-1-thread-19處理任務18 pool-1-thread-20處理任務19 pool-1-thread-21處理任務20 pool-1-thread-25處理任務24 pool-1-thread-24處理任務23 pool-1-thread-23處理任務22 pool-1-thread-22處理任務21 pool-1-thread-26處理任務25 pool-1-thread-27處理任務26 pool-1-thread-28處理任務27 pool-1-thread-30處理任務29 pool-1-thread-29處理任務28 pool-1-thread-31處理任務30 pool-1-thread-32處理任務31 pool-1-thread-33處理任務32 pool-1-thread-38處理任務37 pool-1-thread-35處理任務34 pool-1-thread-36處理任務35 pool-1-thread-41處理任務40 pool-1-thread-34處理任務33 pool-1-thread-39處理任務38 pool-1-thread-40處理任務39 pool-1-thread-37處理任務36 pool-1-thread-42處理任務41 pool-1-thread-43處理任務42 pool-1-thread-45處理任務44 pool-1-thread-46處理任務45 pool-1-thread-44處理任務43 pool-1-thread-47處理任務46 pool-1-thread-50處理任務49 pool-1-thread-48處理任務47 pool-1-thread-49處理任務48
代碼中使用Executors.newCachedThreadPool()
建立線程池,看一下的源碼:
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
從輸出中能夠看出,系統建立了50個線程處理任務,代碼中使用了SynchronousQueue
同步隊列,這種隊列比較特殊,放入元素必需要有另一個線程去獲取這個元素,不然放入元素會失敗或者一直阻塞在那裏直到有線程取走,示例中任務處理休眠了指定的時間,致使已建立的工做線程都忙於處理任務,因此新來任務以後,將任務丟入同步隊列會失敗,丟入隊列失敗以後,會嘗試新建線程處理任務。使用上面的方式建立線程池須要注意,若是須要處理的任務比較耗時,會致使新來的任務都會建立新的線程進行處理,可能會致使建立很是多的線程,最終耗盡系統資源,觸發OOM。
package com.itsoku.chat16; import java.util.concurrent.*; /** * 跟着阿里p7學併發,微信公衆號:javacode2018 */ public class Demo3 { static class Task implements Runnable, Comparable<Task> { private int i; private String name; public Task(int i, String name) { this.i = i; this.name = name; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "處理" + this.name); } @Override public int compareTo(Task o) { return Integer.compare(o.i, this.i); } } public static void main(String[] args) { ExecutorService executor = new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS, new PriorityBlockingQueue()); for (int i = 0; i < 10; i++) { String taskName = "任務" + i; executor.execute(new Task(i, taskName)); } for (int i = 100; i >= 90; i--) { String taskName = "任務" + i; executor.execute(new Task(i, taskName)); } executor.shutdown(); } }
輸出:
pool-1-thread-1處理任務0 pool-1-thread-1處理任務100 pool-1-thread-1處理任務99 pool-1-thread-1處理任務98 pool-1-thread-1處理任務97 pool-1-thread-1處理任務96 pool-1-thread-1處理任務95 pool-1-thread-1處理任務94 pool-1-thread-1處理任務93 pool-1-thread-1處理任務92 pool-1-thread-1處理任務91 pool-1-thread-1處理任務90 pool-1-thread-1處理任務9 pool-1-thread-1處理任務8 pool-1-thread-1處理任務7 pool-1-thread-1處理任務6 pool-1-thread-1處理任務5 pool-1-thread-1處理任務4 pool-1-thread-1處理任務3 pool-1-thread-1處理任務2 pool-1-thread-1處理任務1
輸出中,除了第一個任務,其餘任務按照優先級高低按順序處理。緣由在於:建立線程池的時候使用了優先級隊列,進入隊列中的任務會進行排序,任務的前後順序由Task中的i變量決定。向PriorityBlockingQueue
加入元素的時候,內部會調用代碼中Task的compareTo
方法決定元素的前後順序。
給線程池中線程起一個有意義的名字,在系統出現問題的時候,經過線程堆棧信息能夠更容易發現系統中問題所在。自定義建立工廠須要實現java.util.concurrent.ThreadFactory
接口中的Thread newThread(Runnable r)
方法,參數爲傳入的任務,須要返回一個工做線程。
示例代碼:
package com.itsoku.chat16; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; /** * 跟着阿里p7學併發,微信公衆號:javacode2018 */ public class Demo4 { static AtomicInteger threadNum = new AtomicInteger(1); public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10), r -> { Thread thread = new Thread(r); thread.setName("自定義線程-" + threadNum.getAndIncrement()); return thread; }); for (int i = 0; i < 5; i++) { String taskName = "任務-" + i; executor.execute(() -> { System.out.println(Thread.currentThread().getName() + "處理" + taskName); }); } executor.shutdown(); } }
輸出:
自定義線程-1處理任務-0 自定義線程-3處理任務-2 自定義線程-2處理任務-1 自定義線程-4處理任務-3 自定義線程-5處理任務-4
代碼中在任務中輸出了當前線程的名稱,能夠看到是咱們自定義的名稱。
經過jstack查看線程的堆棧信息,也能夠看到咱們自定義的名稱,咱們能夠將代碼中executor.shutdown();
先給註釋掉讓程序先不退出,而後經過jstack查看,以下:
當線程池中隊列已滿,而且線程池已達到最大線程數,線程池會將任務傳遞給飽和策略進行處理。這些策略都實現了RejectedExecutionHandler
接口。接口中有個方法:
void rejectedExecution(Runnable r, ThreadPoolExecutor executor)
參數說明:
r:須要執行的任務
executor:當前線程池對象
JDK中提供了4種常見的飽和策略:
AbortPolicy:直接拋出異常
CallerRunsPolicy:在當前調用者的線程中運行任務,即隨丟來的任務,由他本身去處理
DiscardOldestPolicy:丟棄隊列中最老的一個任務,即丟棄隊列頭部的一個任務,而後執行當前傳入的任務
DiscardPolicy:不處理,直接丟棄掉,方法內部爲空
須要實現RejectedExecutionHandler
接口。任務沒法處理的時候,咱們想記錄一下日誌,咱們須要自定義一個飽和策略,示例代碼:
package com.itsoku.chat16; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * 跟着阿里p7學併發,微信公衆號:javacode2018 */ public class Demo5 { static class Task implements Runnable { String name; public Task(String name) { this.name = name; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "處理" + this.name); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String toString() { return "Task{" + "name='" + name + '\'' + '}'; } } public static void main(String[] args) { ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1), Executors.defaultThreadFactory(), (r, executors) -> { //自定義飽和策略 //記錄一下沒法處理的任務 System.out.println("沒法處理的任務:" + r.toString()); }); for (int i = 0; i < 5; i++) { executor.execute(new Task("任務-" + i)); } executor.shutdown(); } }
輸出:
沒法處理的任務:Task{name='任務-2'} 沒法處理的任務:Task{name='任務-3'} pool-1-thread-1處理任務-0 沒法處理的任務:Task{name='任務-4'} pool-1-thread-1處理任務-1
輸出結果中能夠看到有3個任務進入了飽和策略中,記錄了任務的日誌,對於沒法處理多任務,咱們最好可以記錄一下,讓開發人員可以知道。任務進入了飽和策略,說明線程池的配置可能不是太合理,或者機器的性能有限,須要作一些優化調整。
線程池提供了2個關閉方法:shutdown
和shutdownNow
,當調用者兩個方法以後,線程池會遍歷內部的工做線程,而後調用每一個工做線程的interrrupt方法給線程發送中斷信號,內部若是沒法響應中斷信號的可能永遠沒法終止,因此若是內部有無線循環的,最好在循環內部檢測一下線程的中斷信號,合理的退出。調用者兩個方法中任意一個,線程池的isShutdown
方法就會返回true,當全部的任務線程都關閉以後,才表示線程池關閉成功,這時調用isTerminaed
方法會返回true。
調用shutdown
方法以後,線程池將再也不接口新任務,內部會將全部已提交的任務處理完畢,處理完畢以後,工做線程自動退出。
而調用shutdownNow
方法後,線程池會將還未處理的(在隊裏等待處理的任務)任務移除,將正在處理中的處理完畢以後,工做線程自動退出。
至於調用哪一個方法來關閉線程,應該由提交到線程池的任務特性決定,多數狀況下調用shutdown
方法來關閉線程池,若是任務不必定要執行完,則能夠調用shutdownNow
方法。
雖然jdk提供了ThreadPoolExecutor
這個高性能線程池,可是若是咱們本身想在這個線程池上面作一些擴展,好比,監控每一個任務執行的開始時間,結束時間,或者一些其餘自定義的功能,咱們應該怎麼辦?
這個jdk已經幫咱們想到了,ThreadPoolExecutor
內部提供了幾個方法beforeExecute
、afterExecute
、terminated
,能夠由開發人員本身去這些方法。看一下線程池內部的源碼:
try { beforeExecute(wt, task);//任務執行以前調用的方法 Throwable thrown = null; try { task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown);//任務執行完畢以後調用的方法 } } finally { task = null; w.completedTasks++; w.unlock(); }
beforeExecute:任務執行以前調用的方法,有2個參數,第1個參數是執行任務的線程,第2個參數是任務
protected void beforeExecute(Thread t, Runnable r) { }
afterExecute:任務執行完成以後調用的方法,2個參數,第1個參數表示任務,第2個參數表示任務執行時的異常信息,若是無異常,第二個參數爲null
protected void afterExecute(Runnable r, Throwable t) { }
terminated:線程池最終關閉以後調用的方法。全部的工做線程都退出了,最終線程池會退出,退出時調用該方法
示例代碼:
package com.itsoku.chat16; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 跟着阿里p7學併發,微信公衆號:javacode2018 */ public class Demo6 { static class Task implements Runnable { String name; public Task(String name) { this.name = name; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "處理" + this.name); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String toString() { return "Task{" + "name='" + name + '\'' + '}'; } } public static void main(String[] args) throws InterruptedException { ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1), Executors.defaultThreadFactory(), (r, executors) -> { //自定義飽和策略 //記錄一下沒法處理的任務 System.out.println("沒法處理的任務:" + r.toString()); }) { @Override protected void beforeExecute(Thread t, Runnable r) { System.out.println(System.currentTimeMillis() + "," + t.getName() + ",開始執行任務:" + r.toString()); } @Override protected void afterExecute(Runnable r, Throwable t) { System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + ",任務:" + r.toString() + ",執行完畢!"); } @Override protected void terminated() { System.out.println(System.currentTimeMillis() + "," + Thread.currentThread().getName() + ",關閉線程池!"); } }; for (int i = 0; i < 10; i++) { executor.execute(new Task("任務-" + i)); } TimeUnit.SECONDS.sleep(1); executor.shutdown(); } }
輸出:
1564324574847,pool-1-thread-1,開始執行任務:Task{name='任務-0'} 1564324574850,pool-1-thread-3,開始執行任務:Task{name='任務-2'} pool-1-thread-3處理任務-2 1564324574849,pool-1-thread-2,開始執行任務:Task{name='任務-1'} pool-1-thread-2處理任務-1 1564324574848,pool-1-thread-5,開始執行任務:Task{name='任務-4'} pool-1-thread-5處理任務-4 1564324574848,pool-1-thread-4,開始執行任務:Task{name='任務-3'} pool-1-thread-4處理任務-3 1564324574850,pool-1-thread-7,開始執行任務:Task{name='任務-6'} pool-1-thread-7處理任務-6 1564324574850,pool-1-thread-6,開始執行任務:Task{name='任務-5'} 1564324574851,pool-1-thread-8,開始執行任務:Task{name='任務-7'} pool-1-thread-8處理任務-7 pool-1-thread-1處理任務-0 pool-1-thread-6處理任務-5 1564324574851,pool-1-thread-10,開始執行任務:Task{name='任務-9'} pool-1-thread-10處理任務-9 1564324574852,pool-1-thread-9,開始執行任務:Task{name='任務-8'} pool-1-thread-9處理任務-8 1564324576851,pool-1-thread-2,任務:Task{name='任務-1'},執行完畢! 1564324576851,pool-1-thread-3,任務:Task{name='任務-2'},執行完畢! 1564324576852,pool-1-thread-1,任務:Task{name='任務-0'},執行完畢! 1564324576852,pool-1-thread-4,任務:Task{name='任務-3'},執行完畢! 1564324576852,pool-1-thread-8,任務:Task{name='任務-7'},執行完畢! 1564324576852,pool-1-thread-7,任務:Task{name='任務-6'},執行完畢! 1564324576852,pool-1-thread-5,任務:Task{name='任務-4'},執行完畢! 1564324576853,pool-1-thread-6,任務:Task{name='任務-5'},執行完畢! 1564324576853,pool-1-thread-10,任務:Task{name='任務-9'},執行完畢! 1564324576853,pool-1-thread-9,任務:Task{name='任務-8'},執行完畢! 1564324576853,pool-1-thread-9,關閉線程池!
從輸出結果中能夠看到,每一個須要執行的任務打印了3行日誌,執行前由線程池的beforeExecute
打印,執行時會調用任務的run
方法,任務執行完畢以後,會調用線程池的afterExecute
方法,從每一個任務的首尾2條日誌中能夠看到每一個任務耗時2秒左右。線程池最終關閉以後調用了terminated
方法。
要想合理的配置線程池,須要先分析任務的特性,能夠衝一下幾個角度分析:
任務的性質:CPU密集型任務、IO密集型任務和混合型任務
任務的優先級:高、中、低
任務的執行時間:長、中、短
任務的依賴性:是否依賴其餘的系統資源,如數據庫鏈接。
性質不一樣任務能夠用不一樣規模的線程池分開處理。CPU密集型任務應該儘量小的線程,如配置cpu數量+1個線程的線程池。因爲IO密集型任務並非一直在執行任務,不能讓cpu閒着,則應配置儘量多的線程,如:cup數量*2。混合型的任務,若是能夠拆分,將其拆分紅一個CPU密集型任務和一個IO密集型任務,只要這2個任務執行的時間相差不是太大,那麼分解後執行的吞吐量將高於串行執行的吞吐量。能夠經過Runtime.getRuntime().availableProcessors()
方法獲取cpu數量。優先級不一樣任務能夠對線程池採用優先級隊列來處理,讓優先級高的先執行。
使用隊列的時候建議使用有界隊列,有界隊列增長了系統的穩定性,若是採用無解隊列,任務太多的時候可能致使系統OOM,直接讓系統宕機。
線程池彙總線程大小對系統的性能有必定的影響,咱們的目標是但願系統可以發揮最好的性能,過多或者太小的線程數量沒法有消息的使用機器的性能。咋Java Concurrency inPractice書中給出了估算線程池大小的公式:
Ncpu = CUP的數量 Ucpu = 目標CPU的使用率,0<=Ucpu<=1 W/C = 等待時間與計算時間的比例 爲保存處理器達到指望的使用率,最有的線程池的大小等於: Nthreads = Ncpu × Ucpu × (1+W/C)
在《阿里巴巴java開發手冊》中指出了線程資源必須經過線程池提供,不容許在應用中自行顯示的建立線程,這樣一方面是線程的建立更加規範,能夠合理控制開闢線程的數量;另外一方面線程的細節管理交給線程池處理,優化了資源的開銷。而線程池不容許使用Executors去建立,而要經過ThreadPoolExecutor方式,這一方面是因爲jdk中Executor框架雖然提供瞭如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等建立線程池的方法,但都有其侷限性,不夠靈活;另外因爲前面幾種方法內部也是經過ThreadPoolExecutor方式實現,使用ThreadPoolExecutor有助於你們明確線程池的運行規則,建立符合本身的業務場景須要的線程池,避免資源耗盡的風險。
線程池中的全部線程超過了空閒時間都會被銷燬麼?
若是allowCoreThreadTimeOut爲true,超過了空閒時間的全部線程都會被回收,不過這個值默認是false,系統會保留核心線程,其餘的會被回收
空閒線程是如何被銷燬的?
全部運行的工做線程會嘗試從隊列中獲取任務去執行,超過必定時間(keepAliveTime)尚未拿到任務,本身主動退出
核心線程在線程池建立的時候會初始化好麼?
默認狀況下,核心線程不會進行初始化,在剛開始調用線程池執行任務的時候,傳入一個任務會建立一個線程,直到達到核心線程數。不過能夠在建立線程池以後,調用其prestartAllCoreThreads
提早將核心線程建立好。
高併發系列連載中,感興趣的加我微信itsoku,一塊兒交流,關注公衆號:路人甲Java,天天獲取最新連載文章!