Java中常用的四種執行緒池

  • 2019 年 10 月 28 日
  • 筆記

在Java中使用執行緒池,可以用ThreadPoolExecutor的構造函數直接創建出執行緒池實例,如何使用參見之前的文章Java執行緒池構造參數詳解。不過,在Executors類中,為我們提供了常用執行緒池的創建方法。接下來我們就來了解常用的四種:

newFixedThreadPool

首先,看一下這種執行緒池的創建方法:

public static ExecutorService newFixedThreadPool(int nThreads) {          return new ThreadPoolExecutor(nThreads, nThreads,                                        0L, TimeUnit.MILLISECONDS,                                        new LinkedBlockingQueue<Runnable>());  }

從構造方法可以看出,它創建了一個固定大小的執行緒池,每次提交一個任務就創建一個執行緒,直到執行緒達到執行緒池的最大值nThreads。執行緒池的大小一旦達到最大值後,再有新的任務提交時則放入無界阻塞隊列中,等到有執行緒空閑時,再從隊列中取出任務繼續執行。
那麼,如何使用newFixedThreadPool呢?我們來舉個例子:

public class OneMoreStudy {      public static void main(String[] args) {          ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);            for (int i = 0; i < 5; i++) {              final int index = i;              fixedThreadPool.execute(new Runnable() {                      @Override                      public void run() {                          try {                              SimpleDateFormat sdf = new SimpleDateFormat(                                      "HH:mm:ss");                              System.out.println("運行時間: " +                                  sdf.format(new Date()) + " " + index);                              Thread.sleep(2000);                          } catch (InterruptedException e) {                              e.printStackTrace();                          }                      }                  });          }            fixedThreadPool.shutdown();      }  }

上面的例子中創建了一個固定大小為3的執行緒池,然後在執行緒池提交了5個任務。在提交第4個任務時,因為執行緒池的大小已經達到了3並且前3個任務在運行中,所以第4個任務被放入了隊列,等待有空閑的執行緒時再被運行。運行結果如下(注意前3個任務和後2個任務的運行時間):

運行時間: 08:09:02 1  運行時間: 08:09:02 2  運行時間: 08:09:02 0  運行時間: 08:09:04 4  運行時間: 08:09:04 3

newCachedThreadPool

首先,看一下這種執行緒池的創建方法:

public static ExecutorService newCachedThreadPool() {          return new ThreadPoolExecutor(0, Integer.MAX_VALUE,                                        60L, TimeUnit.SECONDS,                                        new SynchronousQueue<Runnable>());      }

從構造方法可以看出,它創建了一個可快取的執行緒池。當有新的任務提交時,有空閑執行緒則直接處理任務,沒有空閑執行緒則創建新的執行緒處理任務,隊列中不儲存任務。執行緒池不對執行緒池大小做限制,執行緒池大小完全依賴於作業系統(或者說JVM)能夠創建的最大執行緒大小。如果執行緒空閑時間超過了60秒就會被回收。
那麼,如何使用newCachedThreadPool呢?我們來舉個例子:

public class OneMoreStudy {      public static void main(String[] args) {          ExecutorService cachedThreadPool = Executors.newCachedThreadPool();            for (int i = 0; i < 5; i++) {              final int index = i;              cachedThreadPool.execute(new Runnable() {                      @Override                      public void run() {                          try {                              SimpleDateFormat sdf = new SimpleDateFormat(                                      "HH:mm:ss");                              System.out.println("運行時間: " +                                  sdf.format(new Date()) + " " + index);                              Thread.sleep(2000);                          } catch (InterruptedException e) {                              e.printStackTrace();                          }                      }                  });          }            cachedThreadPool.shutdown();      }  }

因為這種執行緒有新的任務提交,就會創建新的執行緒(執行緒池中沒有空閑執行緒時),不需要等待,所以提交的5個任務的運行時間是一樣的,運行結果如下:

運行時間: 08:45:18 2  運行時間: 08:45:18 1  運行時間: 08:45:18 3  運行時間: 08:45:18 4  運行時間: 08:45:18 0

newSingleThreadExecutor

首先,看一下這種執行緒池的創建方法:

public static ExecutorService newSingleThreadExecutor() {          return new FinalizableDelegatedExecutorService              (new ThreadPoolExecutor(1, 1,                                      0L, TimeUnit.MILLISECONDS,                                      new LinkedBlockingQueue<Runnable>()));  }

從構造方法可以看出,它創建了一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序執行。
那麼,如何使用newSingleThreadExecutor呢?我們來舉個例子:

public class OneMoreStudy {      public static void main(String[] args) {          ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();            for (int i = 0; i < 5; i++) {              final int index = i;              singleThreadExecutor.execute(new Runnable() {                      @Override                      public void run() {                          try {                              SimpleDateFormat sdf = new SimpleDateFormat(                                      "HH:mm:ss");                              System.out.println("運行時間: " +                                  sdf.format(new Date()) + " " + index);                              Thread.sleep(2000);                          } catch (InterruptedException e) {                              e.printStackTrace();                          }                      }                  });          }            singleThreadExecutor.shutdown();      }  }

因為該執行緒池類似於單執行緒執行,所以先執行完前一個任務後,再順序執行下一個任務,
運行結果如下:

運行時間: 08:54:17 0  運行時間: 08:54:19 1  運行時間: 08:54:21 2  運行時間: 08:54:23 3  運行時間: 08:54:25 4

有的同學可能會質疑:既然類似於單執行緒執行,那麼這種執行緒池還有存在的必要嗎?這裡的單執行緒執行指的是執行緒池內部,從執行緒池外的角度看,主執行緒在提交任務到執行緒池時並沒有阻塞,仍然是非同步的。

newScheduledThreadPool

這個方法創建了一個固定大小的執行緒池,支援定時及周期性任務執行。
首先看一下定時執行的例子:

public class OneMoreStudy {      public static void main(String[] args) {          final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");          ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);          System.out.println("提交時間: " + sdf.format(new Date()));          scheduledThreadPool.schedule(new Runnable() {                  @Override                  public void run() {                      System.out.println("運行時間: " + sdf.format(new Date()));                  }              }, 3, TimeUnit.SECONDS);          scheduledThreadPool.shutdown();      }  }

使用該執行緒池的schedule方法,延遲3秒鐘後執行任務,運行結果如下:

提交時間: 09:11:39  運行時間: 09:11:42

再看一下周期執行的例子:

public class OneMoreStudy {      public static void main(String[] args) {          final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");          ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);          System.out.println("提交時間: " + sdf.format(new Date()));          scheduledThreadPool.scheduleAtFixedRate(new Runnable() {                  @Override                  public void run() {                      System.out.println("運行時間: " + sdf.format(new Date()));                  }              }, 1, 3, TimeUnit.SECONDS);          Thread.sleep(10000);          scheduledThreadPool.shutdown();      }  }

使用該執行緒池的scheduleAtFixedRate方法,延遲1秒鐘後每隔3秒執行一次任務,運行結果如下:

提交時間: 09:23:20  運行時間: 09:23:21  運行時間: 09:23:24  運行時間: 09:23:27

Tags: