【Java】A Guide to the Java ExecutorService

Xander發表於2023-03-26

引言

中文翻譯是ExecutorService使用指南,整體看下來入門但是能透過本文快速概覽和學習Executors的使用。

ExecutorService是JDK的一個API,它簡化了非同步模式下的任務執行。一般來說,ExecutorService會自動提供一個執行緒池和一個用於向其分配任務的API。

1. Java ExecutorService 指南

A Guide to the Java ExecutorService | Baeldung

2. Instantiating ExecutorService

2.1. Factory Methods of the Executors Class

最簡單的構建執行緒池的方法是使用 Executors 工廠類預設的工廠方法。舉個例子,比如下面的方法構建一個大小為10的執行緒池:

ExecutorService executor = Executors.newFixedThreadPool(10);

更多關於這個工廠類的用法可以閱讀:https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Executors.html

2.2. Directly Create an ExecutorService

ExecutorService 是典型的面向介面設計,對應的所有實現類都可以作為返回結果,從jaav.util.concurrent的包構建ExecutorService的實現(比如Executors工廠方法),當然也可以像下面這樣直接實現:

/**  
 * 不推薦使用除開  
 */  
@Deprecated  
private static void newExecutorService() {  
    ExecutorService executorService =  
            new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,  
                    new LinkedBlockingQueue<Runnable>());  
}

但是這種寫法實際上和Executors的寫法沒有相差多少,本著不重複造輪子以及避免犯錯的理念,這裡不建議開發者自行構建ExecutorService實現,除非對併發類設計非常瞭解。

3. Assigning Tasks to the ExecutorService

ExecutorService可以執行Runnable和Callable任務。下面的案例當中,為了保持簡單將使用兩個原始的任務。請注意在這裡使用lambda表示式而不是匿名的內部類實現。


public static void main(String[] args) throws ExecutionException, InterruptedException {  

        assignTaskToExecutor();  
    }

/**  
 * 不推薦使用除開  
 */  
@Deprecated  
private static ExecutorService newExecutorService() {  
    ExecutorService executorService =  
            new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,  
                    new LinkedBlockingQueue<Runnable>());  
    return executorService;  
}

public static void assignTaskToExecutor(){  
      // 傳統 Runnable 任務
      Runnable runnableTask = () -> {  
        try {  
            System.out.println(Thread.currentThread().getName()+" runnableTask");  
            TimeUnit.MILLISECONDS.sleep(3000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    };  
    ExecutorService executorService = newExecutorService();  
executorService.execute(runnableTask);

    // Callable 任務
    Callable<String> callableTask = () -> {  
        System.out.println(Thread.currentThread().getName()+" callableTask");  
        TimeUnit.MILLISECONDS.sleep(300);  
        return "Task's execution "+Thread.currentThread().getName();  
    };
    List<Callable<String>> callableTasks = new ArrayList<>();  
    callableTasks.add(callableTask);  
    callableTasks.add(callableTask);  
    callableTasks.add(callableTask);  
}

我們可以使用這幾個方法將任務分配給ExecutorService,包括execute(),這是從Executor介面繼承的,還有submit()、invokeAny()和 invokeAll()。

execute() 是沒有返回結果的,void 的返回值導致沒有任何獲取返回結果的途徑。

executorService.execute(runnableTask);  
Future<?> submit = executorService.submit(runnableTask);  
// runable返回結果為null  
Object o = submit.get();  
System.out.println(o);  
/**  
 pool-1-thread-1 runnableTask 
 pool-1-thread-1 runnableTask 
 Runnable的返回結果為null
 null 
 
*/

submit() 將一個Callable或Runnable任務提交給一個ExecutorService,並返回一個Future型別的結果。

for (Callable<String> task : callableTasks) {  
  
    Future<?> callResult = executorService.submit(task);  
    Object callRes = callResult.get();  
    System.err.println(callRes);  
}/**  
 pool-1-thread-1 callableTask Task's execution pool-1-thread-1 callableTask Task's execution pool-1-thread-1 callableTask Task's execution */

invokeAny() 將一個任務集合分配給一個ExecutorService,使每個任務執行,並返回(任意)一個任務成功執行的結果(如果有一個成功的執行)。

System.out.println("\n");  
String result = executorService.invokeAny(callableTasks);  
System.err.println(result);  
/**  
 pool-1-thread-1 callableTask 
 pool-1-thread-1 callableTask 
 Task's executionpool-1-thread-1 
 */

invokeAll() 每次執行將一個任務集合分配給一個ExecutorService,並以Future型別的物件列表的形式,返回所有任務的執行結果。

List<Future<String>> futures = executorService.invokeAll(callableTasks);  
for (Future<String> future : futures) {  
    System.out.println("收到回撥" + future.get());  
}/**  
 pool-1-thread-1 callableTask 
 pool-1-thread-1 callableTask 
pool-1-thread-1 callableTask
  收到回撥Task's execution pool-1-thread-1  
 收到回撥Task's execution pool-1-thread-1  
 收到回撥Task's execution pool-1-thread-1  
 */

在進一步討論之前,我們還需要討論另外兩個話題:關閉ExecutorService處理Future返回型別

4. Shutting Down an ExecutorService

通常情況下 ExecutorService 的執行緒池是不會自動關閉的,如果當前執行緒池沒有任務,就會一直等待直到有新任務進入。

在某些情況下,這是很有幫助的,例如當一個應用程式需要處理不定期出現的任務,或者在編譯時不知道任務數量。

但是另一方面又會因為這種看似“無用”的等待,佔用JVM的執行緒而導致一個應用程式一直處於執行狀態。

Idea中會發現程式並不會結束。

為了正確關閉ExecutorService,我們要使用 shutdown()shutdownNow() 的 API。

問題來了,兩者的區別是什麼?

shutdown():方法並不會導致ExecutorService的立即銷燬。它將使ExecutorService停止接受新的任務,並在所有執行的執行緒完成其當前工作後關閉(延後處理)。也就是類似“中斷”的方式關閉執行緒池。

// 關閉執行緒池  
executorService.shutdown();

shutdownNow():shutdownNow()方法試圖立即銷燬ExecutorService,但它不能保證所有正在執行的執行緒都能同時停止。

作為“補償”,shutdownNow() 方法提供了返回值,所有沒有執行完成的執行緒任何組裝為一個List返回,有開發者決定如何處理這些沒有完成的任務。

這裡需要注意,如果執行緒中存在有可能中斷異常的任務,比如Sleep,將會出現shutdownNow()之後丟擲異常的情況,此時任務將會被中斷。
//具備返回值
        List<Runnable> runnables = executorService.shutdownNow();
        /**
         * 如果是一個很長的睡眠任務,則會中斷
         * java.lang.InterruptedException: sleep interrupted
         *     at java.base/java.lang.Thread.sleep(Native Method)
         *     at java.base/java.lang.Thread.sleep(Thread.java:339)
         *     at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
         *     at com.zxd.interview.executoreervicetest.ExecutorServiceTest.lambda$assignTaskToExecutor$2(ExecutorServiceTest.java:111)
         *     at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
         *     at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
         *     at java.base/java.lang.Thread.run(Thread.java:834)
         * */
        for (Runnable runnable : runnables) {
            Thread thread = new Thread(runnable);
            thread.setName("處理未結束內容");
            thread.run();
            System.out.println("補償,繼續執行 " );
        }

再次強調,shutdownNow() 方法返回一個等待處理的任務列表,由開發者來決定如何處理這些任務。

關閉ExecutorService的最佳實踐(這也是Oracle推薦的)是使用這兩個方法與 awaitTermination() 方法相結合。比如下面的程式碼:

executorService.shutdown();  
  
// 最佳實踐 shutdownNow 和 awaitTermination 方法結合使用  
try {  
    if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {  
        executorService.shutdownNow();  
    }  
} catch (InterruptedException e) {  
    // 防止當前執行操作執行緒被其他執行緒中斷
    executorService.shutdownNow();  
}

使用這種方法,ExecutorService將首先停止接受新的任務,然後在指定的時間內等待所有任務的完成。如果這個時間過了,執行就會立即停止。

5. The Future Interface

上面介紹了submit()invokeAll() 將會返回物件或者返回 Future 型別的集合,透過 Future 可以實現當前執行緒等待執行緒池的任務執行回撥通知結果,或者透過Future 獲取當前任務的執行狀態。

這一套API屬於典型的NIO非同步阻塞的執行緒模型,Future介面提供了一個特殊的阻塞方法get(),它返回Callable任務的實際執行結果,如果是Runnable任務則返回null

Future<String> future = executorService.submit(callableTask);
String result = null;
try {
    result = future.get();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

在任務仍在執行時呼叫get()方法將導致執行阻塞,直到任務正確執行且結果可用。另外由於get()方法造成的阻塞時間很長,應用程式的效能會下降,如果產生的資料不是很關鍵,可以透過使用超時來避免這種問題。

String result = future.get(200, TimeUnit.MILLISECONDS);

如果執行時間超過了指定的時間(在本例中是200毫秒),將丟擲一個TimeoutException。PS:我們可以使用 isDone 判斷當前分配的任務是否已經執行完成。

List<Future<String>> futures = executorService.invokeAll(callableTasks);  
for (Future<String> future : futures) {  
    System.out.println("是否完成:"+ future.isDone());  
    System.out.println("收到回撥:" + future.get());  
}
/**  
 * 判斷是否完成  
 * pool-1-thread-1 callableTask  
 * pool-1-thread-1 callableTask * pool-1-thread-1 callableTask * 是否完成:true  
 * 收到回撥:Task's execution pool-1-thread-1  
 * 是否完成:true  
 * 收到回撥:Task's execution pool-1-thread-1  
 * 是否完成:true  
 * 收到回撥:Task's execution pool-1-thread-1  
 * */

Future介面還提供了用 cancel() 方法取消任務的執行,並用 isCancelled() 方法檢查取消情況。為了實驗這個效果,這裡我們需要把前面提到的Callable的任務延長執行時間:


        Future<String> submit1 = executorService2.submit(callableTask3);
        submit1.cancel(true);
        // 如果一個任務 cancel 之後進行get,會丟擲異常
        /*
        Exception in thread "main" java.util.concurrent.CancellationException
        at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:121)
        at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
        at com.zxd.interview.executoreervicetest.ExecutorServiceTest.assignTaskToExecutor(ExecutorServiceTest.java:171)
        at com.zxd.interview.executoreervicetest.ExecutorServiceTest.main(ExecutorServiceTest.java:20)
        * */
//        System.out.println("取消執行任務"+submit1.get());
        System.out.println("是否取消執行任務:"+ submit1.isCancelled());
        /**
         * 是否取消執行任務:true
         * */

6. The ScheduledExecutorService Interface

ScheduledExecutorService 執行緒池可以存放一些定期或者延期執行的任務。依然建議使用Executors工廠構建ScheduledExecutorService:

ScheduledExecutorService executorService = Executors
  .newSingleThreadScheduledExecutor();

要在一個固定(時間)延遲後安排並執行任務,使用 ScheduledExecutorService 的 scheduled() 方法。 scheduled( )方法允許你執行Runnable或Callable任務。

Callable<String> scheduleCall = () -> {  
    System.out.println(Thread.currentThread().getName()+" callableTask");  
    TimeUnit.MILLISECONDS.sleep(300);  
    return "Task's execution "+Thread.currentThread().getName();  
};  
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());  
Future<String> resultFuture =  
        scheduledExecutorService.schedule(scheduleCall, 1, TimeUnit.SECONDS);  
System.out.println("resultFuture => "+ resultFuture.get());  
scheduledExecutorService.shutdown();  
/**  
 * pool-3-thread-1 callableTask resultFuture => Task's execution pool-3-thread-1 * */

scheduleAtFixedRate()方法讓我們在一個固定的延遲時間後執行一個任務。上面的程式碼在執行callableTask之前延遲了一秒

下面的程式碼塊將在100毫秒的初始延遲後執行一個任務。此後它將每隔450毫秒執行一次相同的任務。

如果此時存在關閉操作,這種“週期”任務就會被中斷。
//下面的程式碼塊將在100毫秒的初始延遲後執行一個任務。此後,它將每隔**450毫秒**執行一次相同的任務  
scheduledExecutorService.scheduleAtFixedRate(runnableTask, 100, 450, TimeUnit.MILLISECONDS);  
try {  
    if (!scheduledExecutorService.awaitTermination(4000, TimeUnit.MILLISECONDS)) {  
        scheduledExecutorService.shutdownNow();  
    }  
} catch (InterruptedException e) {  
    scheduledExecutorService.shutdownNow();  
}/**  
 pool-3-thread-1 runnableTask 
 pool-3-thread-2 runnableTask  
 java.lang.InterruptedException: sleep interrupted 
 at java.base/java.lang.Thread.sleep(Native Method) 
 at java.base/java.lang.Thread.sleep(Thread.java:339) 
 at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446) 
 at com.zxd.interview.executoreervicetest.ExecutorServiceTest.lambda$assignTaskToExecutor$0(ExecutorServiceTest.java:39) 
 */

如果處理器需要比 scheduleAtFixedRate() 方法的週期引數更多的時間來執行一個指定的任務,ScheduledExecutorService將等待,直到當前任務完成後再開始下一個。

如果有必要在任務的迭代之間有一個固定長度的延遲,應該使用scheduleWithFixedDelay()。例如,下面的程式碼將保證在當前執行結束和另一個執行開始之間有150毫秒的停頓:

// 下面的程式碼將保證在當前執行結束和另一個執行開始之間有150毫秒的停頓  
scheduledExecutorService.scheduleWithFixedDelay(runnableTask, 100, 150, TimeUnit.MILLISECONDS);

注意任務的週期執行將在ExecutorService終止時或在任務執行期間丟擲異常時結束。比如前面介紹的優雅的關閉執行緒池的方式,遇到週期任務中有可能的中斷操作會導致執行緒進行響應中斷而結束執行。

7. ExecutorService vs Fork/Join

Java7之後出現的Fork/Join 框架成為了ExecutorService框架的進一步替代者。

雖然fork/join的操作使得開發併發程式設計程式變簡單,同時減少了開發者的執行權控制。但是注意這並不是任何時候都是正確的。ExecutorService讓開發者有能力控制生成的執行緒數量以及應該由獨立執行緒執行的任務的粒度。ExecutorService的最佳用例是處理獨立的任務,比如根據 "一個執行緒一個任務 "的方案處理事務或請求,比如多個Sheet的資料使用多個執行緒並行解析和處理。

相較之下,從Oracle的文件來看fork/join的設計更像是是為了加快那些可以遞迴地分解成小塊的工作。

Fork/Join (The Java™ Tutorials > Essential Java Classes > Concurrency) (oracle.com)

儘管ExecutorService相對簡單,但也有一些常見的陷阱。

最後讓我們來總結一下:

  • 未使用的執行緒池會一直存活:參見第4節中關於如何關閉ExecutorService的詳細解釋。
  • 謹慎考慮使用固定長度的執行緒池時,避免錯誤的執行緒池容量。確定應用程式需要多少個執行緒來有效執行任務是非常重要的。過大的執行緒池會導致不必要的開銷,而這些執行緒大多處於等待模式。太少的執行緒會使應用程式看起來沒有反應,因為佇列中的任務有很長的等待時間。
  • 在任務取消後呼叫一個Future的get()方法會怎麼樣?試圖獲取一個已經取消的任務的結果會觸發一個CancellationException。
  • 用Future的get()方法意外地進行長時間阻塞。我們應該使用超時來避免意外的等待。

8. 程式碼

原文的程式碼可以在GitHub倉庫中找到:

tutorials/core-java-modules/core-java-concurrency-simple at master · eugenp/tutorials (github.com)

下面是個人胡亂折騰的案例:


public class ExecutorServiceTest
{

    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        newExecutorService();
        assignTaskToExecutor();
    }

    /**
     * 不推薦使用除開
     */
    @Deprecated
    private static ExecutorService newExecutorService() {
        ExecutorService executorService =
                new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
                        new LinkedBlockingQueue<Runnable>());
        return executorService;
    }

    public static void assignTaskToExecutor() throws ExecutionException, InterruptedException {

        Runnable runnableTask = () -> {
            try {
                System.out.println(Thread.currentThread().getName()+" runnableTask");
                TimeUnit.MILLISECONDS.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Callable<String> callableTask = () -> {
            System.out.println(Thread.currentThread().getName()+" callableTask");
            TimeUnit.MILLISECONDS.sleep(300);
            return "Task's execution "+Thread.currentThread().getName();
        };



        List<Callable<String>> callableTasks = new ArrayList<>();
        callableTasks.add(callableTask);
        callableTasks.add(callableTask);
        callableTasks.add(callableTask);
        ExecutorService executorService = newExecutorService();
        executorService.execute(runnableTask);
        Future<?> submit = executorService.submit(runnableTask);
        // runable返回結果為null
        Object o = submit.get();
        System.out.println(o);
        /**
         pool-1-thread-1 runnableTask
         pool-1-thread-1 runnableTask
         Runnable的返回結果為null
         null
         */

        for (Callable<String> task : callableTasks) {

            Future<?> callResult = executorService.submit(task);
            Object callRes = callResult.get();
            System.err.println(callRes);
        }/**
         pool-1-thread-1 callableTask
         Task's execution
         pool-1-thread-1 callableTask
         Task's execution
         pool-1-thread-1 callableTask
         Task's execution
         */
        System.out.println("\n");
        String result = executorService.invokeAny(callableTasks);
        System.out.println("invokeAny => "+result);
        /**
         pool-1-thread-1 callableTask
         pool-1-thread-1 callableTask
         Task's executionpool-1-thread-1
         */


        System.out.println("\n");
        System.out.println("\n");
        List<Future<String>> futures = executorService.invokeAll(callableTasks);
        for (Future<String> future : futures) {
            System.out.println("是否完成:"+ future.isDone());
            System.out.println("收到回撥:" + future.get());
        }/**
         callAble 的介面實現回撥結果:
         pool-1-thread-1 callableTask
         pool-1-thread-1 callableTask
         pool-1-thread-1 callableTask
         收到回撥Task's execution pool-1-thread-1
         收到回撥Task's execution pool-1-thread-1
         收到回撥Task's execution pool-1-thread-1
         */

        /**
         * 判斷是否完成
         * pool-1-thread-1 callableTask
         * pool-1-thread-1 callableTask
         * pool-1-thread-1 callableTask
         * 是否完成:true
         * 收到回撥:Task's execution pool-1-thread-1
         * 是否完成:true
         * 收到回撥:Task's execution pool-1-thread-1
         * 是否完成:true
         * 收到回撥:Task's execution pool-1-thread-1
         * */

        // 關閉執行緒池
//        executorService.shutdown();
//        Runnable runnableTask2 = () -> {
//            try {
//                System.out.println(Thread.currentThread().getName()+" runnableTask");
//                TimeUnit.MILLISECONDS.sleep(30000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        };
//        executorService.execute(runnableTask2);
        //具備返回值
//        List<Runnable> runnables = executorService.shutdownNow();
        /**
         * 如果是一個很長的睡眠任務,則會中斷
         * java.lang.InterruptedException: sleep interrupted
         *     at java.base/java.lang.Thread.sleep(Native Method)
         *     at java.base/java.lang.Thread.sleep(Thread.java:339)
         *     at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
         *     at com.zxd.interview.executoreervicetest.ExecutorServiceTest.lambda$assignTaskToExecutor$2(ExecutorServiceTest.java:111)
         *     at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
         *     at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
         *     at java.base/java.lang.Thread.run(Thread.java:834)
         * */
//        for (Runnable runnable : runnables) {
//            Thread thread = new Thread(runnable);
//            thread.setName("處理未結束內容");
//            thread.run();
//            System.out.println("補償,繼續執行 " );
//        }


//        executorService.shutdown();


        System.out.println("\n");
        System.out.println("\n");
        // 延長等待時間的回撥任務
        ExecutorService executorService2 = newExecutorService();
        Callable<String> callableTask3 = () -> {
            System.out.println(Thread.currentThread().getName()+" callableTask");
//            TimeUnit.MILLISECONDS.sleep(300);
            // 延長等待時間
            TimeUnit.MILLISECONDS.sleep(30000);
            return "Task's execution "+Thread.currentThread().getName();
        };

        Future<String> submit1 = executorService2.submit(callableTask3);
        submit1.cancel(true);
        // 如果一個任務 cancel 之後進行get,會丟擲異常
        /*
        Exception in thread "main" java.util.concurrent.CancellationException
        at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:121)
        at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
        at com.zxd.interview.executoreervicetest.ExecutorServiceTest.assignTaskToExecutor(ExecutorServiceTest.java:171)
        at com.zxd.interview.executoreervicetest.ExecutorServiceTest.main(ExecutorServiceTest.java:20)
        * */
//        System.out.println("取消執行任務"+submit1.get());
        System.out.println("是否取消執行任務:"+ submit1.isCancelled());
        /**
         * 是否取消執行任務:true
         * */


        // 最佳實踐 shutdownNow 和 awaitTermination 方法結合使用
        try {
            if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
        }


        // 下面的程式碼在執行callableTask之前延遲了一秒。
        System.out.println("\n");
        System.out.println("\n");
        Callable<String> scheduleCall = () -> {
            System.out.println(Thread.currentThread().getName()+" callableTask");
            TimeUnit.MILLISECONDS.sleep(300);
            return "Task's execution "+Thread.currentThread().getName();
        };
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());
        Future<String> resultFuture =
                scheduledExecutorService.schedule(scheduleCall, 1, TimeUnit.SECONDS);
        System.out.println("resultFuture => "+ resultFuture.get());
        /**
         *
         pool-3-thread-1 callableTask
         resultFuture => Task's execution pool-3-thread-1
         * */

        System.out.println("\n");
        //下面的程式碼塊將在100毫秒的初始延遲後執行一個任務。此後,它將每隔**450毫秒**執行一次相同的任務
        scheduledExecutorService.scheduleAtFixedRate(runnableTask, 100, 450, TimeUnit.MILLISECONDS);


        // 下面的程式碼將保證在當前執行結束和另一個執行開始之間有150毫秒的停頓
        scheduledExecutorService.scheduleWithFixedDelay(runnableTask, 100, 150, TimeUnit.MILLISECONDS);

        /*

        注意任務的週期執行將在ExecutorService**終止**時或在任務執行期間**丟擲異常**時結束。
        pool-3-thread-1 runnableTask
pool-3-thread-2 runnableTask
pool-3-thread-3 runnableTask
pool-3-thread-2 runnableTask
java.lang.InterruptedException: sleep interrupted
    at java.base/java.lang.Thread.sleep(Native Method)
    at java.base/java.lang.Thread.sleep(Thread.java:339)
    at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
    at com.zxd.interview.executoreervicetest.ExecutorServiceTest.lambda$assignTaskToExecutor$0(ExecutorServiceTest.java:39)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.runAndReset$$$capture(FutureTask.java:305)
    at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java)
    at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)
java.lang.InterruptedException: sleep interrupted
    at java.base/java.lang.Thread.sleep(Native Method)
    at java.base/java.lang.Thread.sleep(Thread.java:339)
    at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
    at com.zxd.interview.executoreervicetest.ExecutorServiceTest.lambda$assignTaskToExecutor$0(ExecutorServiceTest.java:39)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.runAndReset$$$capture(FutureTask.java:305)
    at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java)
    at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)
        * */

        try {
            if (!scheduledExecutorService.awaitTermination(4000, TimeUnit.MILLISECONDS)) {
                scheduledExecutorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduledExecutorService.shutdownNow();
        }/**
         pool-3-thread-1 runnableTask
         pool-3-thread-2 runnableTask

         java.lang.InterruptedException: sleep interrupted
         at java.base/java.lang.Thread.sleep(Native Method)
         at java.base/java.lang.Thread.sleep(Thread.java:339)
         at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
         at com.zxd.interview.executoreervicetest.ExecutorServiceTest.lambda$assignTaskToExecutor$0(ExecutorServiceTest.java:39)
         */



        executorService2.shutdown();

    }
}

附錄

阿里巴巴手冊如何介紹

.【強制】多執行緒並行處理定時任務時,Timer 執行多個 TimeTask 時,只要其中之一沒有捕獲丟擲的異 常,其它任務便會自動終止執行,使用 ScheduledExecutorService 則沒有這個問題。

.【強制】執行緒池不允許使用 Executors 去建立,而是透過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確執行緒池的執行規則,規避資源耗盡的風險。

說明:Executors 返回的執行緒池物件的弊端如下:
1)FixedThreadPool 和 SingleThreadPool: 允許的請求佇列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。

2)CachedThreadPool: 允許的建立執行緒數量為 Integer.MAX_VALUE,可能會建立大量的執行緒,從而導致 OOM。

3)ScheduledThreadPool:允許的請求佇列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。

相關文章