Kai Chen
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    1
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 牛年自強計畫 Week 2 - SpringBoot - 番外篇 Java 執行緒 Thread Pool ## 【前言】 前面一篇介紹過了 Thread 後,在使用上幫助了工程師解決需要同時處理的事情,但卻衍生一個問題,那就是開出了大量的 Thread 狀況下會不易管理進度,更甚會有漏掉尚在執行中的 Thread 便將程式結束的窘況。 又另一種情境,當有重複性的、大量的工作,需要開 Thread 去處理,但總共要開多少個? 假如某一段時間內湧入了 1000 份工作,要開出 1000 個 Thread 應對? 一個工作就開一新的 Thread 的話容易造成系統過度負擔而降低了處理效能,最常見的就是 Out Of Memory 問題啦~ 是否有一個東西;一個物件;一個類,可以幫助我們更好、更方便的、更快速地管理這些開出來的 Thread 且避免掉上述兩種情境呢? 這一篇將要介紹到的 **Thread Pool** 就要來幫助工程師解決這些困境。 ## 【Thread Pool 介紹】 **Thread Pool** 是一個讓工程師們可以集合 Threads,使其有序地、有限制地逐一處理,並提供許多方法讓工程師得以確認、檢視、甚至定時。 類似 DB Connection Pool,**Thread Pool** 同樣提供設定最大執行量的管理,所有提交進來的 Thread 會被妥善分配在 Queue 當中,並等到有空位的時候執行,結束後的空位會留給排列 Queue 的下一個 Thread 使用。 引用 Baeldung 的文章 ***Introduction to Thread Pools in Java*** 的圖講解 ![](https://i.imgur.com/HMo3sFq.png) 圖片來源: https://www.baeldung.com/thread-pool-java-and-guava ExecutorService 中會有專門集合工作的 Queue,並依照最大上限的 Thread 數量去執行依序的工作,並蒐集這些結果,開發師可以透過呼叫方法,一次性 or 需求性的取得這些結果的回傳集合,接續後續的處理動作。 而這 ExecutorService 又是什麼呢? ## 【Executor 類家族介紹】 Java Executor 類能夠幫助開發者將 Task 和 Thread 做分離,達到任務歸任務類、執行歸執行類的方法進行解耦,下面是 Kai 做的關係圖,帶大家快速認識這幾個類的繼承、實作邏輯~ ![](https://i.imgur.com/uqUSHtx.png) **【Executor】** 是 Java 用來實作 Thread Pool 的 Interface,在這 Interface 中訂定了 execute() 最基本的執行方法,剛方法。 **【ExecutorService】** 繼承了 **Executor**,並訂定了基本完整的 Thread Pool 操作方法,包含執行、停止、取得結果等,為後續衍生的 **Executor 類**奠定了運行處理 Thread Pool 的基礎。 方法使用說明如下: |方法|使用說明| | ---- | ---- | | **submit** | 送入 Runnable or Callable 類並執行,不論有無回傳值,都會在完成後回傳一個 Future 類。代入參數部分可用 Lambda 方式處理為 ()->Function(Object) 形式,將 "把類別處理為 Runnable" 的流程交給 Lambda 糖果包解決| | **invokeAll** | 一次執行所有的 Task,回傳所有接收的 Future 類 | | **invokeAny** | 一次執行所有的 Task,回傳最快接收的 Future 類 | | **shutdown** | 阻止 submit 新的任務,不會中斷等待中的任務以及執行中的任務;對於定期任務而言會因失去 interrupted 狀態而繼續執行造成錯誤,需要針對任務本身的 **ScheduledFuture** 作 **cancel(true)** 的處理 | | **shutdownNow** | 阻止 submit 新的任務、取消等待中的任務、嘗試中斷執行中的任務;對於定期任務而言會因失去 interrupted 狀態而繼續執行造成錯誤,需要針對任務本身的 **ScheduledFuture** 作 **cancel(true)** 的處理 | | **isShutdown** | 判斷是否為 shutdown 狀況 | | **isTerminating** | 判斷是否為正在超時等待狀況 | | **isTerminated** | 判斷是否結束超時等待狀況 | | **awaitTermination** | 設定等待機制,將會偵測是否仍有執行中的 Task,若**有**則啟動超時等待且返回 **false**;若**無**則會接續執行後續程式且返回 **true**。通常編寫在執行 **shutdown** 和 **shutdownNow** 之後,作為時間限制的最後一道防線使用 | > shutdown 和 shutdownNow 無法阻止定期任務持續運作的問題請洽這篇文章 [Java线程池的shutdownnow()方法为什么不能停止运行的任务?](https://blog.csdn.net/mucaoyx/article/details/109153721) **【AbstractExecutorService】** 實作了 submit() 方法,並在其中使用會回傳 **RunnableFuture** 的 newTaskFor() 方法,然後接續 execute() 進行執行的動作。其餘則有不少新的參數和方法進行定義,並交由 **ThreadPoolExecutor** 進行實現。 方法使用說明如下: |方法|使用說明| | ---- | ---- | | **newTaskFor** | 在 submit() 中執行包裝 Runnable or Callable 並回傳 RunnableFuture 類,交由 executor() 執行 | **【ThreadPoolExecutor】** 實作了 **Queue** 的執行流程,並額外提供了諸多用於確認 ThreadPool 目前狀態、參數的方法,是最完整、最常被使用的 **Executor 類**。 |方法|使用說明| | ---- | ---- | | **newFixedThreadPool** | 建構一個全新的 ThreadPool 並給予最大可同時執行 Thread 數量的限制 | | **getActivityCount** | 返回目前執行中的 Thread 數量,最大上限為 Thread 允許數量 | | **getPoolSize** | 返回設定的最大可同時執行 Thread 數量值 | | **getQueue** | 返回存放 Tasks 的 Queue 物件,可透過 getQueue().size() 取得等待值行的 Tasks 數量 | | **getCompletedTaskCount** | 返回已完成的 Tasks 數量 | **【ScheduledExecutorService】** 另外一種為了實現時間控制功能而衍生的 Executor 類,其實作與定義的方法與 AbstractExecutorService 有諸多不同,但因實務面上甚少使用,只需要了解即可。 方法使用說明如下: |方法|使用說明| | ---- | ---- | | **schedule** | 放入一個 work、時間參數、時間單位,Task 會在經過 時間參數 * 時間單位後,將 Task 放入空的 Thread 執行,若當下沒有空的 Thread,則進行等待,直到有了之後馬上被分配執行| **【ScheduledThreadPoolExecutor】** 實作了 **ScheduledExecutorService** 以及繼承 **ThreadPoolExecutor**,可當作處理執行時間和頻率的 **ThreadPoolExecutor** 強化版,但因功能面相的不同,因此不會在不需要強調時間控制的部分去使用這個類別。 方法使用說明如下: |方法|使用說明| | ---- | ---- | | **scheduleAtFixedRate** | 設定執行頻率。※ 若前一次 Task 尚未結束 | | **scheduleWithFixedDelay**| 設定每一次執行後,多久會再次執行。| ## 【Executors】 **Executors** 類,是**獨立**於上述所有類的存在,其本身提供許多建立 ThreadPool 的 Static 方法,幫助開發者可以簡單的去建立各式各樣的 ThreadPool 類實體。 方法使用說明如下: |方法|使用說明| | ---- | ---- | | **newFixedThreadPool** | 建立一個有最大上限的 Thread Pool,並回傳 | | **newCachedThreadPool**| 建立一個帶有緩存的 Thread Pool,並回傳| | **newSingleThreadExecutor** | 建立一個僅有單一實體的 Thread Pool,並回傳 | | **newScheduledThreadPool**| 建立一個可以安排時程處理的 Thread Pool,並回傳| Kai 馬上運用了 Executors 類在下面的範例中。 ## 【範例】 **build.gradle** ```xml= plugins { id 'org.springframework.boot' version '2.3.3.RELEASE' id 'io.spring.dependency-management' version '1.0.8.RELEASE' id 'java' } group 'org.example' version '1.0-SNAPSHOT' repositories { mavenCentral() } configurations.all { exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' } dependencies { implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' implementation 'org.junit.jupiter:junit-jupiter-engine:5.6.0' implementation 'org.springframework.boot:spring-boot-starter-log4j2' } test { useJUnitPlatform() } ``` **log4j.properties** ```xml= appender.console.type = Console appender.console.name = STDOUT appender.console.layout.type = PatternLayout appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n rootLogger.level = DEBUG rootLogger.appenderRefs = stdout rootLogger.appenderRef.stdout.ref = STDOUT ``` **test.java** ```java= package Thread.ThreadPool; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class test { static Logger logger = LoggerFactory.getLogger(test.class); @DisplayName("Test ThreadPool : Test Case 1 : Normal Loop Check") @Test public void testCase1() { ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2); threadPoolExecutor.submit(() -> { Thread.sleep(3000); return null; }); threadPoolExecutor.submit(() -> { Thread.sleep(3000); return null; }); threadPoolExecutor.submit(() -> { Thread.sleep(3000); return null; }); threadPoolExecutor.submit(() -> { Thread.sleep(9000); return null; }); while(threadPoolExecutor.getActiveCount() > 0){ logger.debug("Pool Size: " + threadPoolExecutor.getPoolSize()); logger.debug("Activity Threads: " + threadPoolExecutor.getActiveCount()); logger.debug("Queue Size: " + threadPoolExecutor.getQueue().size()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } @DisplayName("Test ThreadPool : Test Case 2 : awaitTermination Check") @Test public void testCase2() { ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2); threadPoolExecutor.submit(() -> { Thread.sleep(3000); return null; }); threadPoolExecutor.submit(() -> { Thread.sleep(3000); return null; }); threadPoolExecutor.submit(() -> { Thread.sleep(3000); return null; }); threadPoolExecutor.submit(() -> { Thread.sleep(9000); return null; }); /* ThreadPoolExecutor before using shutdown(), all false */ logger.debug("isTerminated: " + threadPoolExecutor.isTerminated()); logger.debug("isTerminating: " + threadPoolExecutor.isTerminating()); threadPoolExecutor.shutdown(); /* ThreadPoolExecutor after using shutdown(), * isShutdown() => true; isTerminated() => false; isTerminating() => false */ logger.debug("isShutdown: " + threadPoolExecutor.isShutdown()); logger.debug("isTerminated: " + threadPoolExecutor.isTerminated()); logger.debug("isTerminating: " + threadPoolExecutor.isTerminating()); try { /* awaitTermination encounter executing task, return false */ logger.debug("awaitTermination: "+threadPoolExecutor.awaitTermination(3, TimeUnit.SECONDS)); } catch (InterruptedException e) { e.printStackTrace(); } /* ThreadPoolExecutor after using shutdown(), * isTerminated() => false; isTerminating() => true */ logger.debug("isTerminated: " + threadPoolExecutor.isTerminated()); logger.debug("isTerminating: " + threadPoolExecutor.isTerminating()); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } try { /* all tasks finished, return true */ logger.debug("awaitTermination: "+threadPoolExecutor.awaitTermination(3, TimeUnit.SECONDS)); } catch (InterruptedException e) { e.printStackTrace(); } /* ThreadPoolExecutor after using shutdown(), * isTerminated() => true; isTerminating() => false */ logger.debug("isTerminated: " + threadPoolExecutor.isTerminated()); logger.debug("isTerminating: " + threadPoolExecutor.isTerminating()); } @BeforeAll public static void BeforeAll(){ logger.debug("Test Start"); } @AfterAll public static void AfterAll(){ logger.debug("Test End"); } } ``` 範例中設計了 testCase1 和 testCase2 分別展示了使用一般方式處理判定 ThreadPool 執行的狀況以及透過 **awaitTermination** 方式的處理。 testCase2 則很明白的呈現了 **isShutdown()**、**isTerminated()**、**isTerminating()** 的使用差別。 > 別忘了,**shutdown()** 或 **shutdonwNow()** 幾乎都會搭配 **awaitTermination()** 一起使用。 而不論 **isShutdown()**、**isTerminated()**、**isTerminating()**,都無法用來作為判定 ThreadPool 是否已完成所有 Tasks 的參數,因此還是要使用到 **getActivityCount()**,通常沒有正在執行的任務時,就可以視為已完成所有任務,若任務因各種原因 Delay、暫緩、無回應則應該要有各被執行任務的一些 Feedback 去處理,而不是在 ThreadPool 這邊加功夫。 首頁 [Kai 個人技術 Hackmd](/2G-RoB0QTrKzkftH2uLueA) ###### tags: `Spring Boot`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully