線程池的五種狀態及建立線程池的幾種方式

在這裏插入圖片描述java

上篇《Java線程的6種狀態詳解及建立線程的4種方式
前言:咱們都知道,線程是稀有資源,系統頻繁建立會很大程度上影響服務器的使用效率,若是不加以限制,很容易就會把服務器資源耗盡。因此,咱們能夠經過建立線程池來管理這些線程,提高對線程的使用率。面試

一、什麼是線程池?

簡而言之,線程池就是管理線程的一個容器,有任務須要處理時,會相繼判斷核心線程數是否還有空閒、線程池中的任務隊列是否已滿、是否超過線程池大小,而後調用或建立線程或者排隊,線程執行完任務後並不會當即被銷燬,而是仍然在線程池中等待下一個任務,若是超過存活時間尚未新的任務就會被銷燬,經過這樣複用線程從而下降開銷。數組

二、使用線程池有什麼優勢?

可能有人就會問了,使用線程池有什麼好處嗎?那不用說,好處天然是有滴。大概有如下:
一、提高線程池中線程的使用率,減小對象的建立、銷燬。
二、線程池的伸縮性對性能有較大的影響,使用線程池能夠控制線程數,有效的提高服務器的使用資源,避免因爲資源不足而發生宕機等問題。(建立太多線程,將會浪費必定的資源,有些線程未被充分使用;銷燬太多線程,將致使以後浪費時間再次建立它們;建立線程太慢,將會致使長時間的等待,性能變差;銷燬線程太慢,致使其它線程資源飢餓。)緩存

三、線程池的核心工做流程(重要)

咱們要使用線程池得先了解它是怎麼工做的,流程以下圖,廢話很少說看圖就行。核心就是複用線程,下降開銷。
線程池的工做流程安全

四、線程池的五種狀態生命週期

  • RUNNING :能接受新提交的任務,而且也能處理阻塞隊列中的任務。
  • SHUTDOWN:關閉狀態,再也不接受新提交的任務,但卻能夠繼續處理阻塞隊列中已保存的任務。在線程池處於 RUNNING 狀態時,調用 shutdown() 方法會使線程池進入到該狀態。(finalize() 方法在執行過程當中也會調用 shutdown() 方法進入該狀態)。
  • STOP:不能接受新任務,也不處理隊列中的任務,會中斷正在處理任務的線程。在線程池處於 RUNNING 或 SHUTDOWN 狀態時,調用 shutdownNow() 方法會使線程池進入到該狀態。
  • TIDYING:若是全部的任務都已終止了,workerCount (有效線程數) 爲0,線程池進入該狀態後會調用 terminated() 方法進入 TERMINATED 狀態。
  • TERMINATED:在 terminated() 方法執行完後進入該狀態,默認 terminated() 方法中什麼也沒有作。
    線程池的生命週期流程圖

    五、建立線程池的幾種方式

  • 經過 Executors 工廠方法建立
  • 經過 new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) 自定義建立
    相對而言,更建議用第二個建立線程池,Executors 建立的線程池內部不少地方用到了***任務隊列,在高併發場景下,***任務隊列會接收過多的任務對象,嚴重狀況下會致使 JVM 崩潰,一些大廠也是禁止使用 Executors 工廠方法去建立線程池。newFixedThreadPool 和 newSingleThreadExecutor 的主要問題是堆積的請求處理隊列可能會耗費很是大的內存,甚至 OOM;newCachedThreadPool 和 newScheduledThreadPool 的主要問題是線程數最大數是 Integer.MAX_VALUE,可能會建立數量很是多的線程,甚至 OOM。

    5.一、Executors 五個工廠方法建立不一樣線程池的區別

    在這裏插入圖片描述
    一、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 構造方法建立自定義線程池。多線程

    5.二、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));

5.2.一、工做隊列

jdk 中提供了四種工做隊列:
①ArrayBlockingQueue
基於數組的有界阻塞隊列,按 FIFO 排序。新任務進來後,會放到該隊列的隊尾,有界的數組能夠防止資源耗盡問題。當線程池中線程數量達到 corePoolSize 後,再有新任務進來,則會將任務放入該隊列的隊尾,等待被調度。若是隊列已是滿的,則建立一個新線程,若是線程數量已經達到 maxPoolSize,則會執行拒絕策略。
②LinkedBlockingQuene
基於鏈表的***阻塞隊列(其實最大容量爲 Interger.MAX_VALUE),按照 FIFO 排序。因爲該隊列的近似***性,當線程池中線程數量達到 corePoolSize 後,再有新任務進來,會一直存入該隊列,而不會去建立新線程直到 maxPoolSize,所以使用該工做隊列時,參數 maxPoolSize 實際上是不起做用的。
③SynchronousQuene
一個不緩存任務的阻塞隊列,生產者放入一個任務必須等到消費者取出這個任務。也就是說新任務進來時,不會緩存,而是直接被調度執行該任務,若是沒有可用線程,則建立新線程,若是線程數量達到 maxPoolSize,則執行拒絕策略。
④PriorityBlockingQueue
具備優先級的***阻塞隊列,優先級經過參數 Comparator 實現。併發

5.2.二、拒絕策略

當工做隊列中的任務已到達最大限制,而且線程池中的線程數量也達到最大限制,這時若是有新任務提交進來,就會執行拒絕策略。jdk中提供了4中拒絕策略:
①ThreadPoolExecutor.CallerRunsPolicy
該策略下,在調用者線程中直接執行被拒絕任務的 run 方法,除非線程池已經 shutdown,則直接拋棄任務。
②ThreadPoolExecutor.AbortPolicy
該策略下,直接丟棄任務,並拋出 RejectedExecutionException 異常。
③ThreadPoolExecutor.DiscardPolicy
該策略下,直接丟棄任務,什麼都不作。
④ThreadPoolExecutor.DiscardOldestPolicy
該策略下,拋棄進入隊列最先的那個任務,而後嘗試把此次拒絕的任務放入隊列。
除此以外,還能夠根據應用場景須要來實現 RejectedExecutionHandler 接口自定義策略。ide

六、線程池的關閉

  • shutdown():
    一、調用以後不容許繼續往線程池內添加線程;
    二、線程池的狀態變爲 SHUTDOWN 狀態;
    三、全部在調用 shutdown() 方法以前提交到 ExecutorSrvice 的任務都會執行;
    四、一旦全部線程結束執行當前任務,ExecutorService 纔會真正關閉。
  • shutdownNow():
    一、該方法返回還沒有執行的 task 的 List;
    二、線程池的狀態變爲 STOP 狀態;
    三、嘗試中止全部的正在執行或暫停任務的線程。
    簡單點來講,就是:
    shutdown() 調用後,不能夠再 submit 新的 task,已經 submit 的將繼續執行
    shutdownNow() 調用後,試圖中止當前正在執行的 task,並返回還沒有執行的 task 的 list

    七、總結

    本文簡單介紹了線程池的一些相關知識,相信你們對線程池的優勢,線程池的生命週期,線程池的工做流程及線程池的使用有了一個大概的瞭解,也但願能對有須要的人提供一點幫助!文中有錯誤的地方,還請留言給予指正,謝謝~
    也歡迎你們關注個人公衆號:Java的成神之路,免費領取最新面試資料,技術電子書,架構進階相關資料等。
    在這裏插入圖片描述

相關文章
相關標籤/搜索