Java ExecutorService四種線程池的使用

一、Executors提供的四種線程

  1. newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
  2. newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
  3. newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。
  4. newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

 二、下面對四種線程池的使用進行簡單的代碼實現

1. newCachedThreadPool() 創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。 

public static void newCachedThreadPoolTest() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 200; i++) {
            final int index = i;
            try {
                Thread.sleep(index);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executorService.execute(() -> System.out.println(Thread.currentThread().getName() + "," + index));
        }
        //shutdown 的原理是隻是將線程池的狀態設置成SHUTDOWN狀態,然後中斷所有沒有正在執行任務的線程
        //shutdownNow 會首先將線程池的狀態設置成STOP,然後嘗試停止所有的正在執行或暫停任務的線程,並返回等待執行任務的列表
        executorService.shutdown();
    }

控制檯輸出:

注:介紹一下shutdown()和shutdownNow()區別:

線程池的關閉 我們可以通過調用線程池的shutdown或shutdownNow方法來關閉線程池,但是它們的實現原理不

  1. shutdown的原理是隻是將線程池的狀態設置成SHUTDOWN狀態,然後中斷所有沒有正在執行任務的線程。
  2. shutdownNow的原理是遍歷線程池中的工作線程,然後逐個調用線程的interrupt方法來中斷線程,所以無法響應中斷的任務可能永遠無法終止。shutdownNow會首先將線程池的狀態設置成STOP,然後嘗試停止所有的正在執行或暫停任務的線程,並返回等待執行任務的列表。

只要調用了這兩個關閉方法的其中一個,isShutdown方法就會返回true。當所有的任務都已關閉後,才表示線程池關閉成功,這時調用isTerminaed方法會返回true。至於我們應該調用哪一種方法來關閉線程池,應該由提交到線程池的任務特性決定,通常調用shutdown來關閉線程池,如果任務不一定要執行完,則可以調用shutdownNow。

 2. newFixedThreadPool() 創建一個定長的線程池,可控制線程最大併發數,超出的線程會在隊列中等待

public static void newFixedThreadPoolTest(){
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 200; i++) {
            final int index = i;
            fixedThreadPool.execute(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + "," + index);
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            // 以上代碼可以改寫成submit提交任務的lambda表達式的形式
            fixedThreadPool.submit(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + ", " + index);
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, true);
        }
    }

 注:這裏介紹一下使用execute()和submit()方法提交任務的區別:

向線程池提交任務 ThreadPoolExecutor類中execute()和submit()區別

  • execute()方法實際上是Executor中聲明的方法,在ThreadPoolExecutor進行了具體的實現,這個方法是ThreadPoolExecutor的核心方法,通過這個方法可以向線程池提交一個任務,交由線程池去執行。
  • submit()方法是在ExecutorService中聲明的方法,在AbstractExecutorService就已經有了具體的實現,在ThreadPoolExecutor中並沒有對其進行重寫,這個方法也是用來向線程池提交任務的,但是它和execute()方法不同,它能夠返回任務執行的結果,通過源碼查看submit()方法的實現,會發現它實際上還是調用的execute()方法,只不過它利用了Future來獲取任務執行結果。
//submit()源碼
/**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

 3.  newScheduledThreadPool() 創建定長的線程池,支持週期和定時任務

// 示例如下:
    public static void newScheduledThreadPoolTest() {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        System.out.println("before:" + LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() / 1000);
        scheduledThreadPool.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("延遲3秒執行一次:" + System.currentTimeMillis() / 1000);
            }
        }, 3, TimeUnit.SECONDS);
        System.out.println("after:" + LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() / 1000);

    }

    System.out.println("----------------------------華麗的分割線-------------------------");
    System.out.println("begin:" + LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() / 1000);
    // initialDelay:延遲首次執行的時間,period:連續執行之間的時間間隔,unit:initialDelay 和 period的時間單位
    scheduledThreadPool.scheduleAtFixedRate(() -> System.out.println("首次執行時間延遲1s,然後3s執行一次:" + System.currentTimeMillis() / 1000), 1, 3, TimeUnit.SECONDS);
    System.out.println("end:" + LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli() / 1000);

4. newSingleThreadExecutor() 創建一個單線程化的線程池,只會用工作線程來執行任務,保證順序

// 示例如下:
    public static void newSingleThreadExecutorTest(){
        ExecutorService singleThread = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 100; i++) {
            final int index = i;
            singleThread.execute(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + "," + index);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }

三、 線程池分析 

1. 線程池的主要工作流程如下圖:

從上圖我們可知,當提交一個新任務到線程池時,線程池的處理流程如下: 

  1. 首先線程池判斷核心線程池是否已滿?沒滿,創建一個工作線程來執行任務。滿了,則進入下個流程。
  2. 其次線程池判斷工作隊列是否已滿?沒滿,則將新提交的任務存儲在工作隊列裏。滿了,則進入下個流程。
  3. 最後線程池判斷整個線程池是否已滿?沒滿,則創建一個新的工作線程來執行任務,滿了,則交給飽和策略來處理這個任務。

2. 源碼分析。

上面的流程分析讓我們很直觀的瞭解的線程池的工作原理,讓我們再通過源代碼來看看是如何實現的。線程池執行任務的方法如下: 

public void execute(Runnable command) {
  if (command == null)
    throw new NullPointerException();
  int c = ctl.get();
  if (workerCountOf(c) < corePoolSize) {
    if (addWorker(command, true))
      return;
    c = ctl.get();
  }
  if (isRunning(c) && workQueue.offer(command)) {
    int recheck = ctl.get();
    if (! isRunning(recheck) && remove(command))
      reject(command);
    else if (workerCountOf(recheck) == 0)
      addWorker(null, false);
  }
  else if (!addWorker(command, false))
    reject(command);
}

工作線程。線程池創建線程時,會將線程封裝成工作線程Worker,Worker在執行完任務後,還會無限循環獲取工作隊列裏的任務來執行。

參考文章鏈接:https://www.jb51.net/article/134862.htm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章