java
上篇《Java線程的6種狀態詳解及建立線程的4種方式》
前言:咱們都知道,線程是稀有資源,系統頻繁建立會很大程度上影響服務器的使用效率,若是不加以限制,很容易就會把服務器資源耗盡。因此,咱們能夠經過建立線程池來管理這些線程,提高對線程的使用率。面試
簡而言之,線程池就是管理線程的一個容器,有任務須要處理時,會相繼判斷核心線程數是否還有空閒、線程池中的任務隊列是否已滿、是否超過線程池大小,而後調用或建立線程或者排隊,線程執行完任務後並不會當即被銷燬,而是仍然在線程池中等待下一個任務,若是超過存活時間尚未新的任務就會被銷燬,經過這樣複用線程從而下降開銷。數組
可能有人就會問了,使用線程池有什麼好處嗎?那不用說,好處天然是有滴。大概有如下:
一、提高線程池中線程的使用率,減小對象的建立、銷燬。
二、線程池的伸縮性對性能有較大的影響,使用線程池能夠控制線程數,有效的提高服務器的使用資源,避免因爲資源不足而發生宕機等問題。(建立太多線程,將會浪費必定的資源,有些線程未被充分使用;銷燬太多線程,將致使以後浪費時間再次建立它們;建立線程太慢,將會致使長時間的等待,性能變差;銷燬線程太慢,致使其它線程資源飢餓。)緩存
咱們要使用線程池得先了解它是怎麼工做的,流程以下圖,廢話很少說看圖就行。核心就是複用線程,下降開銷。
安全
一、newCachedThreadPool()(工做隊列使用的是 SynchronousQueue)
建立一個線程池,若是線程池中的線程數量過大,它能夠有效的回收多餘的線程,若是線程數不足,那麼它能夠建立新的線程。
不足:這種方式雖然能夠根據業務場景自動的擴展線程數來處理咱們的業務,可是最多須要多少個線程同時處理倒是咱們沒法控制的。
優勢:若是當第二個任務開始,第一個任務已經執行結束,那麼第二個任務會複用第一個任務建立的線程,並不會從新建立新的線程,提升了線程的複用率。
做用:該方法返回一個能夠根據實際狀況調整線程池中線程的數量的線程池。即該線程池中的線程數量不肯定,是根據實際狀況動態調整的。
二、newFixedThreadPool()(工做隊列使用的是 LinkedBlockingQueue)
這種方式能夠指定線程池中的線程數。若是滿了後又來了新任務,此時只能排隊等待。
優勢:newFixedThreadPool 的線程數是能夠進行控制的,所以咱們能夠經過控制最大線程來使咱們的服務器達到最大的使用率,同時又能夠保證即便流量忽然增大也不會佔用服務器過多的資源。
做用:該方法返回一個固定線程數量的線程池,該線程池中的線程數量始終不變,即不會再建立新的線程,也不會銷燬已經建立好的線程,自始自終都是那幾個固定的線程在工做,因此該線程池能夠控制線程的最大併發數。
三、newScheduledThreadPool()
該線程池支持定時,以及週期性的任務執行,咱們能夠延遲任務的執行時間,也能夠設置一個週期性的時間讓任務重複執行。該線程池中有如下兩種延遲的方法。
scheduleAtFixedRate 不一樣的地方是任務的執行時間,若是間隔時間大於任務的執行時間,任務不受執行時間的影響。若是間隔時間小於任務的執行時間,那麼任務執行結束以後,會立馬執行,至此間隔時間就會被打亂。
scheduleWithFixedDelay 的間隔時間不會受任務執行時間長短的影響。
做用:該方法返回一個能夠控制線程池內線程定時或週期性執行某任務的線程池。
四、newSingleThreadExecutor()
這是一個單線程池,至始至終都由一個線程來執行。
做用:該方法返回一個只有一個線程的線程池,即每次只能執行一個線程任務,多餘的任務會保存到一個任務隊列中,等待這一個線程空閒,當這個線程空閒了再按 FIFO 方式順序執行任務隊列中的任務。
五、newSingleThreadScheduledExecutor()
只有一個線程,用來調度任務在指定時間執行。
做用:該方法返回一個能夠控制線程池內線程定時或週期性執行某任務的線程池。只不過和上面的區別是該線程池大小爲 1,而上面的能夠指定線程池的大小。
使用示例:服務器
//建立一個會根據須要建立新線程的線程池 ExecutorService executor= Executors.newCachedThreadPool(); for (int i = 0; i < 20; i++) { executor.submit(new Runnable() { @Override public void run() { System.out.println(i); } }); }
這五種線程池都是直接或者間接獲取的 ThreadPoolExecutor 實例 ,只是實例化時傳遞的參數不同。因此若是 Java 提供的線程池知足不了咱們的需求,咱們能夠經過 ThreadPoolExecutor 構造方法建立自定義線程池。多線程
public ThreadPoolExecutor( int corePoolSize,//線程池核心線程大小 int maximumPoolSize,//線程池最大線程數量 long keepAliveTime,//空閒線程存活時間 TimeUnit unit,//空閒線程存活時間單位,一共有七種靜態屬性(TimeUnit.DAYS天,TimeUnit.HOURS小時,TimeUnit.MINUTES分鐘,TimeUnit.SECONDS秒,TimeUnit.MILLISECONDS毫秒,TimeUnit.MICROSECONDS微妙,TimeUnit.NANOSECONDS納秒) BlockingQueue<Runnable> workQueue,//工做隊列 ThreadFactory threadFactory,//線程工廠,主要用來建立線程(默認的工廠方法是:Executors.defaultThreadFactory()對線程進行安全檢查並命名) RejectedExecutionHandler handler//拒絕策略(默認是:ThreadPoolExecutor.AbortPolicy不執行並拋出異常) )
使用示例:架構
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 20, 2, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5));
jdk 中提供了四種工做隊列:
①ArrayBlockingQueue
基於數組的有界阻塞隊列,按 FIFO 排序。新任務進來後,會放到該隊列的隊尾,有界的數組能夠防止資源耗盡問題。當線程池中線程數量達到 corePoolSize 後,再有新任務進來,則會將任務放入該隊列的隊尾,等待被調度。若是隊列已是滿的,則建立一個新線程,若是線程數量已經達到 maxPoolSize,則會執行拒絕策略。
②LinkedBlockingQuene
基於鏈表的***阻塞隊列(其實最大容量爲 Interger.MAX_VALUE),按照 FIFO 排序。因爲該隊列的近似***性,當線程池中線程數量達到 corePoolSize 後,再有新任務進來,會一直存入該隊列,而不會去建立新線程直到 maxPoolSize,所以使用該工做隊列時,參數 maxPoolSize 實際上是不起做用的。
③SynchronousQuene
一個不緩存任務的阻塞隊列,生產者放入一個任務必須等到消費者取出這個任務。也就是說新任務進來時,不會緩存,而是直接被調度執行該任務,若是沒有可用線程,則建立新線程,若是線程數量達到 maxPoolSize,則執行拒絕策略。
④PriorityBlockingQueue
具備優先級的***阻塞隊列,優先級經過參數 Comparator 實現。併發
當工做隊列中的任務已到達最大限制,而且線程池中的線程數量也達到最大限制,這時若是有新任務提交進來,就會執行拒絕策略。jdk中提供了4中拒絕策略:
①ThreadPoolExecutor.CallerRunsPolicy
該策略下,在調用者線程中直接執行被拒絕任務的 run 方法,除非線程池已經 shutdown,則直接拋棄任務。
②ThreadPoolExecutor.AbortPolicy
該策略下,直接丟棄任務,並拋出 RejectedExecutionException 異常。
③ThreadPoolExecutor.DiscardPolicy
該策略下,直接丟棄任務,什麼都不作。
④ThreadPoolExecutor.DiscardOldestPolicy
該策略下,拋棄進入隊列最先的那個任務,而後嘗試把此次拒絕的任務放入隊列。
除此以外,還能夠根據應用場景須要來實現 RejectedExecutionHandler 接口自定義策略。ide
本文簡單介紹了線程池的一些相關知識,相信你們對線程池的優勢,線程池的生命週期,線程池的工做流程及線程池的使用有了一個大概的瞭解,也但願能對有須要的人提供一點幫助!文中有錯誤的地方,還請留言給予指正,謝謝~
也歡迎你們關注個人公衆號:Java的成神之路,免費領取最新面試資料,技術電子書,架構進階相關資料等。