自建線程池優雅下線

什麼場景下使用線程池

  1. 同步改異步
  2. 提高併發吞吐量
  3. 多步任務串行改並行

什麼場景下不要使用線程池

  1. 系統資源已經接近瓶頸(內存、CPU、IO)
  2. 上游已經有多線程(多線程嵌套、在線程池中創建線程池)

常見線程池

  • (1) newFixedThreadPool
    建立一個線程數量固定的線程池,規定的最大線程數量,超過這個數量之後進來的任務,會放到等待隊列中,如果有空閒線程,則在等待隊列中獲取,遵循先進先出原則。
  • (2) newCacheThreadExecutor
    在覈心線程達到最大值之前,如果繼續有任務進來就會創建新的核心線程,並加入核心線程池;達到最大核心線程數後,新任務進來,優先使用空閒線程,如果沒有空閒線程,則新建臨時線程.
    newCacheThreadExecutor使用的是SynchronousQueue作爲等待隊列,他不保存任何的任務,新的任務加入進來之後,他會創建臨時線程來進行使用。
  • (3) newScheduledThreadPool
    創建一個線程池,該線程池可以計劃在給定的延遲,或週期性地執行。
    使用的就是DelayedWorkQueue作爲等待隊列,中間進行了一定的等待,等待時間過後,繼續執行任務。

阿里線程開發規約

線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
說明:Executors 返回的線程池對象的弊端如下:

  • 1) FixedThreadPool 和 SingleThreadPool:
    允許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
  • 2) CachedThreadPool:
    允許的創建線程數量爲 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。

多線程優雅下線

爲了保障應用重啓過程中異步操作的執行,避免強制退出JVM可能產生的各種問題,我們可以採用關閉鉤子、自定義信號的方式,主動的通知JVM退出,並在JVM關閉前,執行應用程序的一些掃尾工作,進一步保證應用程序可以安全的退出。

線程在終止的過程中,應該先進行操作來清除當前的任務,保持共享數據的一致性,然後再停止。

使用線程池優雅下線需考量

  • 及時終止新增進件
  • 儘量保證執行中的任務能夠執行完成
  • 減少內存中積壓的任務,避免任務無法及時完成

方案

  • 增加關閉鉤子(shutdown hooks),監聽進程中斷信號。
  • 在鉤子中將線程池關閉,拒絕新增任務,並等待線程結束。
  • 背壓:接受任務時控制待處理隊列大小,限制內存中積壓的任務數量,以保證在停機等待時間範圍內能夠處理完。
  • 限流:可以通過Semaphore信號量限流。

使用線程池要將調度和執行分開,執行代碼儘量要有自身完備性。

調度方案:

  • 推模式:(外部觸發):xxljob, crontab,quartz
  • 拉模式:(死循環,timer):數據庫掃表, MQ消費

減少內存中積壓的任務

  • 推模式調度時
    在調度入口做進程狀態檢查,堵塞新任務進件。
  • 拉模式調度時考量點
    在進程得到中斷信號時,及時終止新增任務

拉模式死循環線程終止方案:

  • 用共享變量(shared variable)的方式來設置標誌,通知線程必須終止。這個共享變量的操作必須保證是同步的。在循環起點判斷標誌狀態
  • 處理InterruptedException,在catch中終止循環。

線程池關閉

shutdown()
將線程池狀態置爲SHUTDOWN,並不會立即停止:

  • 停止接收外部submit的任務
  • 內部正在跑的任務和隊列裏等待的任務,會執行完
  • 等到第二步完成後,才真正停止

shutdownNow()
將線程池狀態置爲STOP。企圖立即停止,事實上不一定:

  • 跟shutdown()一樣,先停止接收外部提交的任務
  • 忽略隊列裏等待的任務
  • 嘗試將正在跑的任務interrupt中斷
  • 返回未執行的任務列表

awaitTermination(long timeOut, TimeUnit unit)
當前線程阻塞,直到

  • 等所有已提交的任務(包括正在跑的和隊列中等待的)執行完
  • 或者等超時時間到
  • 或者線程被中斷,拋出InterruptedException

創建線程池參數

ThreadPoolExecutor構造參數:

  • corePoolSize 要保留在池中的線程數,也就是線程池核心池的大小
  • maximumPoolSize 最大線程數
  • keepAliveTime 當線程數大於核心時,此爲終止前多餘的空閒線程等待新任務的最長時間。
  • unit keepAliveTime 參數的時間單位
  • workQueue 用來儲存等待執行任務的隊列。
  • threadFactory 線程工廠
  • handler 默認的拒絕執行處理程序

參考文獻

  1. 關閉線程的正確方法:“優雅”的中斷
  2. JVM安全退出
  3. threadPoolExecutor 中的 shutdown() 、 shutdownNow() 、 awaitTermination() 的用法和區別
  4. ThreadPoolExecutor(五)——線程池關閉相關操作
  5. 【Java】interrupt、interrupted和isInterrupted的區別
  6. Semaphore:如何快速實現一個限流器?- 併發工具類
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章