Java的線程池是怎麼回事?來看看這篇文章吧

語言: CN / TW / HK

theme: healer-readable

作者:孫玉昌,暱稱【一一哥】,另外【壹壹哥】也是我哦

CSDN博客專家、萬粉博主、阿里雲專家博主、掘金優質作者


一. 線程池的思想

我們使用線程的時候就去創建一個線程,這樣實現起來非常簡便,但是就會有一個問題:

如果併發的線程數量很多,並且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程就會大大降低系統的效率,因為頻繁創建線程和銷燬線程需要時間。

那麼有沒有一種辦法使得線程可以複用,就是執行完一個任務,並不被銷燬,而是可以繼續執行其他的任務?在Java中可以通過線程池來達到這樣的效果。

二. 線程池的概念

線程池: 其實就是一個容納多個線程的容器,其中的線程可以反覆使用,省去了頻繁創建線程對象的操作,無需反覆創建線程而消耗過多資源。

三. 線程池的好處

  1. 降低資源消耗。減少了創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個任務。
  2. 提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
  3. 提高線程的可管理性。可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因為消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)

四. 線程池 核心 API

1. 線程池API類關係

Java裏面線程池的頂級接口是java.util.concurrent.Executor,但是嚴格意義上講Executor並不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是java.util.concurrent.ExecutorService,最終實現類為ThreadPoolExecutor,如下圖所示:

2. ThreadPoolExecutor的構造方法

ThreadPoolExecutor提供了四個構造函數(後附參數詳解):

``` //五個參數的構造函數 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue)

//六個參數的構造函數 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory)

//六個參數的構造函數 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler)

//七個參數的構造函數 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) ```

要配置一個線程池是比較複雜的,尤其是對於線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,因此在java.util.concurrent.Executors線程工廠類裏面提供了一些靜態工廠,生成一些常用的線程池。官方建議使用Executors工廠類來創建線程池對象。

3. 創建線程池的4種方法

Executors類中有個創建線程池的方法如下(四種線程池):

  • public static ExecutorService newFixedThreadPool(int nThreads):返回線程池對象。(創建的是有界線程池,也就是池中的線程個數可以指定最大數量)

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

  • 創建緩存線程池(由任務的多少來決定)newCachedThreadPool()

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

  • 創建單線程池 newSingleThreadExecutor(),有且僅有一個工作線程執行任務

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

  • 創建調度線程池(調度:按週期、定時執行)newScheduledThreadPool(int corePoolSize)

``` public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }

public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue()); } ```

五. 線程池參數詳解

1. 核心參數

Java線程池有以下幾個核心參數:

  • int corePoolSize: 該線程池中核心線程數最大值 核心線程: 線程池新建線程的時候,如果當前線程總數小於corePoolSize,則新建的是核心線程,如果超過corePoolSize,則新建的是非核心線程核心線程默認情況下會一直存活在線程池中,即使這個核心線程啥也不幹(閒置狀態)。 如果指定ThreadPoolExecutor的allowCoreThreadTimeOut這個屬性為true,那麼核心線程如果不幹活(閒置狀態)的話,超過一定時間(時長下面參數決定),就會被銷燬掉。
  • int maximumPoolSize: 該線程池中線程總數最大值 線程總數 = 核心線程數 + 非核心線程數。
  • long keepAliveTime: 該線程池中非核心線程閒置超時時長 一個非核心線程,如果不幹活(閒置狀態)的時長超過這個參數所設定的時長,就會被銷燬掉,如果設置allowCoreThreadTimeOut = true,則會作用於核心線程。
  • TimeUnit unit: keepAliveTime的單位。TimeUnit是一個枚舉類型,包括:NANOSECONDS:1微毫秒 = 1微秒 / 1000 MICROSECONDS:1微秒 = 1毫秒 / 1000 MILLISECONDS:1毫秒 = 1秒 /1000 SECONDS:秒 MINUTES:分 HOURS:小時 DAYS:天
  • BlockingQueue workQueue: 該線程池中的任務隊列:維護着等待執行的Runnable對象。當所有的核心線程都在幹活時,新添加的任務會被添加到這個隊列中等待處理,如果隊列滿了,則新建非核心線程執行任務。

2. 常用的workQueue類型

SynchronousQueue:這個隊列接收到任務的時候,會直接提交給線程處理,而不保留它,如果所有線程都在工作怎麼辦?那就新建一個線程來處理這個任務!所以為了保證不出現<線程數達到了maximumPoolSize而不能新建線程>的錯誤,使用這個類型隊列的時候,maximumPoolSize一般指定成Integer.MAX_VALUE,即無限大。

LinkedBlockingQueue:這個隊列接收到任務的時候,如果當前線程數小於核心線程數,則新建線程(核心線程)處理任務;如果當前線程數等於核心線程數,則進入隊列等待。由於這個隊列沒有最大值限制,即所有超過核心線程數的任務都將被添加到隊列中,這也就導致了maximumPoolSize的設定失效,因為總線程數永遠不會超過corePoolSize。

ArrayBlockingQueue:可以限定隊列的長度,接收到任務的時候,如果沒有達到corePoolSize的值,則新建線程(核心線程)執行任務,如果達到了,則入隊等候,如果隊列已滿,則新建線程(非核心線程)執行任務,又如果總線程數到了maximumPoolSize,並且隊列也滿了,則發生錯誤。

DelayQueue:隊列內元素必須實現Delayed接口,這就意味着你傳進去的任務必須先實現Delayed接口。這個隊列接收到任務時,首先先入隊,只有達到了指定的延時時間,才會執行任務。

ThreadFactory threadFactory: 創建線程的方式,這是一個接口,你new他的時候需要實現他的Thread newThread(Runnable r)方法,一般用不上。

RejectedExecutionHandler handler: 拋出異常專用的,比如上面提到的兩個錯誤發生了,就會由這個handler拋出異常。

六. 線程池的 執行 策略

當一個任務被添加進線程池時,會執行以下策略:

  • 線程數量未達到corePoolSize,則新建一個線程(核心線程)執行任務;
  • 線程數量達到了corePools,則將任務移入隊列等待;
  • 隊列已滿,新建線程(非核心線程)執行任務;
  • 隊列已滿,總線程數又達到了maximumPoolSize,就會由(RejectedExecutionHandler)拋出異常。

如果我們獲取到了一個線程池ExecutorService對象,又該怎麼使用呢?我們可以在這裏定義一個使用線程池對象的方法,如下所示:

public Future<?> submit(Runnable task):獲取線程池中的某一個線程對象,並執行任務。

public <T> Future<T> submit(Callable<T> task):獲取線程池中的某一個線程對象,並執行任務。

這裏的Future接口,帶有返回值,可以用來記錄線程任務執行完畢後產生的結果。

. **線程池 使用

1. 使用步驟

如果我們要使用線程池中的線程對象,主要是遵循如下幾個步驟:

  1. 創建線程池對象;
  2. 創建Runnable/Callable接口子類對象;
  3. 提交Runnable/Callable接口子類對象;
  4. 關閉線程池(一般不做)。

2. 代碼案例

以下是線程池的使用案例:

``` public class ExecutorServiceDemo { public static void main(String[] args) throws Exception { //獲取到了線程池 固定長度的線程池 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(100); //可緩存的線程池 //ExecutorService executorService = Executors.newCachedThreadPool(); //單例線程池 //ExecutorService fixedThreadPool = Executors.newSingleThreadExecutor(); //任務調度線程池 // ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

    //創建任務
    MyCallable myCallable =new MyCallable();
    Future<String> submit = fixedThreadPool.submit(myCallable); //future代表的是線程執行之後的返回結果
    fixedThreadPool.submit(myCallable);//執行任務
    fixedThreadPool.submit(myCallable);
    fixedThreadPool.submit(myCallable);
    fixedThreadPool.submit(myCallable);
    //關閉線程池  服務端線程池是不能關閉的
    //fixedThreadPool.shutdown();
}

}

//callable可以得到線程的返回值 public class MyCallable implements Callable { @Override public String call() throws Exception { System.out.println(Thread.currentThread().getName()+"call任務執行了"); Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+"call執行完畢"); return "qfedu"; } } ```

現在你知道Java中的線程池是怎麼回事了嗎?如果你還有其他問題,可以在評論區留言哦。