多線程 - 12.線程池

爲什麼要使用線程池

  1. 減少了創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個任務。
  2. 可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因爲消耗過多的內存
使用newFixedThreadPool線程池

newFixedThreadPool 將創建一個固定長度的線程池

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class test0 {
    public static void main(String[] args){
        ExecutorService executor = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 4; i++){
            Runnable runnable = new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "正在執行");
                }
            };
            executor.execute(runnable);
        }
        executor.shutdown();
    }
}

pool-1-thread-1正在執行
pool-1-thread-2正在執行
pool-1-thread-3正在執行
pool-1-thread-3正在執行

使用newCachedThreadPool線程池

newCachedThreadPool 將創建一個可緩存的線程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class test0 {
    public static void main(String[] args){
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 4; i++){
            Runnable runnable = new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "正在執行");
                }
            };
            executor.execute(runnable);
        }
        executor.shutdown();
    }
}

pool-1-thread-1正在執行
pool-1-thread-3正在執行
pool-1-thread-2正在執行
pool-1-thread-4正在執行

使用newSingleThreadExecutor線程池

newSingleThreadExecutor 能確保依照任務在隊列中的順序來串行執行

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class test0 {
    public static void main(String[] args){
        ExecutorService executor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 4; i++){
            Runnable runnable = new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "正在執行");
                }
            };
            executor.execute(runnable);
        }
        executor.shutdown();
    }
}

pool-1-thread-1正在執行
pool-1-thread-1正在執行
pool-1-thread-1正在執行
pool-1-thread-1正在執行

使用newScheduledThreadPool線程池

newScheduledThreadPool 創建了一個固定長度的線程池,而且以延遲或定時或週期的方式來執行任務,類似於 Timer、定時任務。可應用於重發機制。此代碼會無限循環輸出。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class test0 {
    public static void main(String[] args){
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
        for (int i = 0; i < 2; i++){
            final int index = i;
            Runnable runnable = new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "延時 1s 後,每 2s 執行一次工作任務--- >" + index);
                }
            };
            executor.scheduleAtFixedRate(runnable,1,2,TimeUnit.SECONDS);
        }
        if (executor.isShutdown()) {
            executor.shutdown();
        }
    }
}

pool-1-thread-1延時 1s 後,每 2s 執行一次工作任務— >0
pool-1-thread-2延時 1s 後,每 2s 執行一次工作任務— >1
pool-1-thread-2延時 1s 後,每 2s 執行一次工作任務— >0
pool-1-thread-1延時 1s 後,每 2s 執行一次工作任務— >1
pool-1-thread-3延時 1s 後,每 2s 執行一次工作任務— >0
pool-1-thread-2延時 1s 後,每 2s 執行一次工作任務— >1
pool-1-thread-1延時 1s 後,每 2s 執行一次工作任務— >0
pool-1-thread-3延時 1s 後,每 2s 執行一次工作任務— >1
pool-1-thread-2延時 1s 後,每 2s 執行一次工作任務— >0
… …

常用的使用方法是調用 Executors 中的靜態方法來創建一個連接池。java 自帶的線程池僅有 4 種:

  • newFixedThreadPool
  • newCachedThreadPool
  • newSingleThreadExecutor
  • newScheduledThreadPool

以上四種創建線程池的方法其實都是調用java.util.concurrent.ThreadPoolExecutor 的構造函數,只是參數不一樣:

  • corePoolSize : 核心線程數
  • maximumPoolSize : 最大線程數
  • keepAliveTime : 當線程數大於核心時,此爲終止前多餘的空閒線程等待新任務的最長時間
  • unit : 時間單位
  • workQueue : 用於存儲工作工人的隊列
  • threadFactory : 創建線程的工廠
  • handler : 由於超出線程範圍和隊列容量而使執行被阻塞時所使用的處理程序

常用的幾種隊列

  • ArrayBlockingQueue:規定大小的 BlockingQueue , 其構造必須指定大小。其所含的對象是 FIFO 順序排序的。
  • LinkedBlockingQueue : 大小不固定的 BlockingQueue , 若其構造時指定大小,生成的 BlockingQueue 有大小限制,不指定大小,其大小有 Integer.MAX_VALUE 來決定。其所含的對象是 FIFO 順序排序的。
  • PriorityBlockingQueue :類似於 LinkedBlockingQueue , 但是其所含對象的排序不是 FIFO,而是依據對象的自然順序或者構造函數的 Comparator 決定。
  • SynchronizedQueue:特殊的 BlockingQueue,對其的操作必須是放和取交替完成。

排隊策略

  • 直接提交。工作隊列的默認選項是 SynchronousQueue,它將任務直接提交給線程而不保持它們。在此,如果不存在可用於立即運行任務的線程,則試圖把任務加入隊列將失敗,因此會構造一個新的線程。此策略可以避免在處理可能具有內部依賴性的請求集時出現鎖。直接提交通常要求無界 maximumPoolSizes 以避免拒絕新提交的任務。當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性。

  • 無界隊列。使用無界隊列(例如,不具有預定義容量的 LinkedBlockingQueue)將導致在所有 corePoolSize 線程都忙時新任務在隊列中等待。這樣,創建的線程就不會超過 corePoolSize。(因此,maximumPoolSize 的值也就無效了。)當每個任務完全獨立於其他任務,即任務執行互不影響時,適合於使用無界隊列;例如,在 Web 頁服務器中。這種排隊可用於處理瞬態突發請求,當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性。

  • 有界隊列。當使用有限的 maximumPoolSizes 時,有界隊列(如 ArrayBlockingQueue)有助於防止資源耗盡,但是可能較難調整和控制。隊列大小和最大池大小可能需要相互折衷:使用大型隊列和小型池可以最大限度地降低 CPU 使用率、操作系統資源和上下文切換開銷,但是可能導致人工降低吞吐量。如果任務頻繁阻塞(例如,如果它們是 I/O 邊界),則系統可能爲超過您許可的更多線程安排時間。使用小型隊列通常要求較大的池大小,CPU 使用率較高,但是可能遇到不可接受的調度開銷,這樣也會降低吞吐量。

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