天天看點

java多線程的實作方式(線程池)

作者:懷揣夢想的閉眼聽風

【問題:為什麼使用線程池】

許多伺服器應用程式都面向處理來自某些遠端來源的大量短小的任務,每當一個請求到達就建立一個新線程,然後在新線程中為請求服務,但是頻繁建立新線程、銷毀新線程、線程切換既花費較多的時間,影響相應速度,又消耗大量的系統資源,且有時伺服器無法處理過多請求導緻崩潰。假設一個伺服器完成一項任務所需時間為:T1 建立線程時間,T2 線上程中執行任務的時間,T3 銷毀線程時間。 如果:T1 + T3 遠大于 T2,則可以采用線程池,以提高伺服器性能。例如:ExecutorService是一個線程池,請求到達時,線程已經存在,響應延遲低,多個任務複用線程,避免了線程的重複建立和銷毀,并且可以規定線程數目,請求數目超過門檻值時強制其等待直到有空閑線程。

【了解ThreadPoolExecutor的參數】

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }           

corePoolSize : 核心線程數,一旦建立将不會再釋放。如果建立的線程數還沒有達到指定的核心線程數量,将會繼續建立新的核心線程,直到達到最大核心線程數後,核心線程數将不再增加;如果沒有空閑的核心線程,同時又未達到最大線程數,則将繼續建立非核心線程;如果核心線程數等于最大線程數,則當核心線程都處于激活狀态時,任務将被挂起,等待有空閑線程時再執行。

maximumPoolSize : 最大線程數,允許建立的最大線程數量。如果最大線程數等于核心線程數,則無法建立非核心線程;如果非核心線程處于空閑時,超過設定的空閑時間,則将被回收,釋放占用的資源。

keepAliveTime : 也就是當線程空閑時,所允許儲存的最大時間,超過這個時間,線程将被釋放銷毀,但隻針對于非核心線程。

unit : 時間機關,TimeUnit.SECONDS等。

workQueue : 任務隊列,用于儲存等待執行的任務的阻塞隊列。可以選擇以下幾個阻塞隊列。

  • ArrayBlockingQueue:是一個基于數組結構的有界阻塞隊列,必須設定容量。此隊列按 FIFO(先進先出)原則對元素進行排序。
  • LinkedBlockingQueue:一個基于連結清單結構的阻塞隊列,可以設定容量,此隊列按FIFO (先進先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。
  • SynchronousQueue:一個不存儲元素的阻塞隊列。每個插入offer操作必須等到另一個線程調用移除poll操作,否則插入操作一直處于阻塞狀态,吞吐量通常要高于LinkedBlockingQueue。
  • PriorityBlockingQueue:一個具有優先級的無限阻塞隊列。

通過檢視Executors

java多線程的實作方式(線程池)

Executors

一共有四種線程池:

  1. FixedThreadPool固定線程數線程池
  2. SingleThreadExecutor單線程池
  3. CachedThreadPool可緩存線程池
  4. ScheduledThreadPool 固定線程數,支援定時和周期性任務線程池

【FixedThreadPool】

java多線程的實作方式(線程池)

FixedThreadPool

coresize和maxmumsize相同,逾時時間為0,隊列用的LinkedBlockingQueue無界的FIFO隊列,如果隊列裡面有線程任務的話就從隊列裡面取出線程,然後開啟一個新的線程開始執行。 很明顯,這個線程池始終隻有size的線程在運作,大小固定,難以擴充。

public static void main(String[] args) throws Exception{
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(){
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ":固定線程執行任務");
                }
            };
            executorService.execute(thread);
        }
        executorService.shutdown();
    }           
pool-1-thread-2:固定線程執行任務
pool-1-thread-1:固定線程執行任務
pool-1-thread-1:固定線程執行任務
pool-1-thread-2:固定線程執行任務
pool-1-thread-1:固定線程執行任務           

結果可發現一直是2個固定線程在執行

【SingleThreadExecutor】

java多線程的實作方式(線程池)

SingleThreadExecutor

隻用一個線程來執行任務,保證任務按FIFO順序一個個執行。

public static void main(String[] args) throws Exception{
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(){
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ":單線程執行任務");
                }
            };
            executorService.execute(thread);
        }
        executorService.shutdown();
    }           
pool-1-thread-1:單線程執行任務
pool-1-thread-1:單線程執行任務
pool-1-thread-1:單線程執行任務
pool-1-thread-1:單線程執行任務
pool-1-thread-1:單線程執行任務           

結果可發現一直是1個固定線程在執行

【CachedThreadPool】

java多線程的實作方式(線程池)

CachedThreadPool

通過它的建立方式可以知道,建立的都是非核心線程,而且最大線程數為Interge的最大值,空閑線程存活時間是1分鐘。SynchronousQueue隊列,一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作。是以,當我們送出第一個任務的時候,是加入不了隊列的,這就滿足了,一個線程池條件“當無法加入隊列的時候,且任務沒有達到maxsize時,我們将新開啟一個線程任務”。即當線程不夠用的時候會不斷建立新線程,如果線程無限增長,會導緻記憶體溢出。是以我們的maxsize是big big。時間是60s,當一個線程沒有任務執行會暫時儲存60s逾時時間,如果沒有的新的任務的話,會從cache中remove掉。是以長時間不送出任務的CachedThreadPool不會占用系統資源。就是緩沖區為1的生産者消費者模式。

60s銷毀例子:

public static void main(String[] args) throws Exception{
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(){
                @Override
                public void run() {
                    try {
                        Thread.sleep(500);
                        System.out.println(Thread.currentThread().getName() + ":緩存線程執行任務");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            executorService.execute(thread);
        }

        System.out.println(executorService);

        Thread.sleep(60000);
        System.out.println(executorService);

        executorService.shutdown();
    }           
java.util.concurrent.ThreadPoolExecutor@2d363fb3[Running, pool size = 5, active threads = 5, queued tasks = 0, completed tasks = 0]
pool-1-thread-4:緩存線程執行任務
pool-1-thread-2:緩存線程執行任務
pool-1-thread-5:緩存線程執行任務
pool-1-thread-3:緩存線程執行任務
pool-1-thread-1:緩存線程執行任務
java.util.concurrent.ThreadPoolExecutor@2d363fb3[Running, pool size = 5, active threads = 0, queued tasks = 0, completed tasks = 5]
           
java多線程的實作方式(線程池)

60s

【ScheduledThreadPool】

java多線程的實作方式(線程池)

ScheduledThreadPool

延遲執行列子

public static void main(String[] args) throws Exception{
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);

        Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + ":延遲線程執行任務,目前時間:"+ LocalDateTime.now());
            }
        };

        System.out.println("開始時間:"+LocalDateTime.now());
        scheduledExecutorService.schedule(thread,5L,TimeUnit.SECONDS);

        scheduledExecutorService.shutdown();
    }           
開始時間:2023-02-16T15:17:40.158
pool-1-thread-1:延遲線程執行任務,目前時間:2023-02-16T15:17:45.167

Process finished with exit code 0           
java多線程的實作方式(線程池)

延遲5s

延遲5秒執行