執行緒管理

eacape發表於2021-04-03

執行緒組

執行緒組(ThreadGroup)可以用來表示一系列相似或者相關的執行緒集合。

一個執行緒組可以包含多個執行緒和執行緒組,一個執行緒組包含另外的執行緒組那麼這個執行緒組被稱為另外這些執行緒組的父執行緒組,如過一個執行緒沒有被關聯執行緒組,那麼這個執行緒就屬於其父執行緒所屬的執行緒組,java虛擬機器在建立main執行緒的時候會為其指定一個執行緒組,所以java中任何一個執行緒都有與之關聯的執行緒組,我們為您可以通過Thread.getThreadGroup()來獲取當前執行緒所關聯的執行緒組。

執行緒的未捕獲異常與監控

當執行緒在run的時候出現未捕獲的異常,那麼導致run方法退出,且相應的執行緒也將會終止。對於這種情況我們可以線上程啟動之前為其關聯一個UncaughtExceptionHandler,當執行緒出現未捕獲異常丟擲的時候, 執行緒會在退出之前呼叫UncaughtExceptionHandler.UncaughtException(Thread t, Throwable e)方法,我們可以在這個方法中進行相應的日誌處理,或者重新建立一個執行緒來替代這個執行緒。

public class UncaughtExceptionDemo {
    static class MyExceptionHandler implements Thread.UncaughtExceptionHandler{

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println(t.getName()+"執行緒丟擲了異常");
            MyThread thread = new MyThread();
            thread.setName("B");
            thread.setUncaughtExceptionHandler(new MyExceptionHandler());
            thread.start();
        }
    }

    static class MyThread extends Thread{
        @Override
        public void run() {
            if ("A".equals(Thread.currentThread().getName())){
                throw new BusinessException("出現異常:"+Thread.currentThread().getName());
            }
            System.out.println(Thread.currentThread().getName());
        }
    }

    static class BusinessException extends RuntimeException{
        public BusinessException(String message) {
            System.out.println(message);
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.setName("A");
        myThread.setUncaughtExceptionHandler(new MyExceptionHandler());
        myThread.start();
    }
}
===========結果============
出現異常:A
A執行緒丟擲了異常
B

執行緒池

使用執行緒池的好處

  • 降低資源消耗:執行緒池中存有可以複用的執行緒,可以減少執行緒 建立-銷燬 所帶來的效能消耗
  • 提高執行緒響應速度:執行緒池中的的執行緒直接響應任務,而不是響應任務後還要建立執行緒
  • 有利於執行緒的管理

執行緒池中各個引數的含義

執行緒池中各個元件的職責及流程

  1. 執行緒池管理器,負責執行緒池的建立、銷燬、新增任務等
  2. 工作執行緒
  3. 任務佇列,用來臨時儲存任務,可以起到緩衝的作用,因為一般處於併發場景,所以任務佇列一般採用BlockingQueue來保障執行緒安全。
  4. 任務,任務要求實現統一介面,以便工作執行緒可以執行和處理

什麼是拒絕策略

  1. 執行緒池呼叫shutdown後,執行緒池會等待當前執行緒池中的所有執行緒執行完,但不會接收新的任務。
  2. 核心執行緒池滿了 - 任務佇列滿了 - 執行緒池達到了最大執行緒數 - 執行緒池不會接收新的任務。

常見的幾種拒絕策略

  1. DiscardPolicy:會直接忽視新加進來的任務
  2. DiscardOldestPolicy:會移除佇列中的隊頭任務,將新的任務插到隊尾
  3. AbortPolicy:會丟擲一個名為RejectedExecutionException的執行時異常,可以在catch中重新處理這個任務
  4. CallerPolicy:有新任務但是執行緒池沒有能力處理時,由提交任務的執行緒執行。(最完善)

常見執行緒池

  1. FixedThreadPool

    固定執行緒數的執行緒池,核心執行緒數和最大執行緒數相同new FixedThreadPool(10) - 最開始執行緒數從0開始增加當增加到10後就不再增加,有新的任務就放到任務佇列當中,當再有新的任務就不會有新的執行緒產生。

  2. CachedThreadPool

    執行緒池中的執行緒可以無限增加,但不會超過 Integr.MAX\_VALUE(2 ^ 31,幾乎不會達到)

  3. ScheduleThreadPool

    週期性執行任務

  4. SingleThreadExecutor

    只有一個執行緒來執行任務,但是當前執行緒出現異常時,執行緒池會新建立一個執行緒執行後續任務,好處就是執行的任務是有序的。

  5. SingleThreadScheduleExecutor

    與3及其相似是ScheduleThreadPool的一個特例, 相當於 new ScheduledThreadPoolExecutor(1)

  6. ForkJoinPool

    常用於遞迴場景,例如樹遍歷

    RecursiveTask 類是對ForkJoinTask 的一個簡單的包裝,這時我們重寫 compute() 方法,當 n<=1時直接返回,當 n>1 就建立遞迴任務,也就是 f1 和 f2,然後我們用 fork() 方法分裂任務並分別執行最後在 return 的時候,使用 join() 方法把結果彙總,這樣就實現了任務的分裂和彙總。

    class Fibonacci extends RecursiveTask<Integer> { 
        int n;
    
        public Fibonacci(int n) { 
            this.n = n;
        } 
    
        @Override
        public Integer compute() { 
            if (n <= 1) { 
                return n;
            } 
            Fibonacci f1 = new Fibonacci(n - 1);
            f1.fork();
            Fibonacci f2 = new Fibonacci(n - 2);
            f2.fork();
            return f1.join() + f2.join();
        } 
     }
     
    public static void main(String[] args){ 
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        for (int i = 0; i < 10; i++) { 
            ForkJoinTask task = forkJoinPool.submit(new Fibonacci(i));
            System.out.println(task.get());
        } 
     }

為什麼不建議使用Executors建立執行緒池

下圖是各個執行緒池對應使用的阻塞佇列

  1. FixedThreadPool和SingleThreadExecutor

    因為LinkBlockingQueue容量幾乎是無限大的,如果任務的消費速度遠超過任務生產速度會導致佇列中堆積大量的任務有可能會導致記憶體溢位。

  2. CatchedThreadPool

    當任務較多的時候,執行緒池會無限制的新建執行緒,最終可能導致作業系統無法新建執行緒或者是記憶體溢位

  3. ScheduleThreadPool和SingleThreadScheduleExecutor

    DelayedWorkQueue也是一個無界佇列,如果佇列中存在大量的任務同樣可能記憶體溢位。

如何正確關閉執行緒池

  1. shutdown

    呼叫這個方法後,執行緒池不會立即停止而是等佇列中的所有任務處理完畢後才會徹底關閉,如果此時還有新的任務提交將會被拒絕。

  2. shutdownNow

    立即終止執行緒池中的所有操作,不安全,不建議使用

  3. isShutdown

    判斷是否執行了shutdown方法,此時可能執行緒池還在執行剩餘任務。

  4. isTerminated

    是否徹底終止了,即執行了shoutdown+剩餘任務被執行完 或者 執行了shutdownNow

  5. awaitTermination

    awaitTermination(10) - 等待10秒,10秒內任務都執行完畢 返回 true,10秒內並未執行完畢 返回 false

執行緒池監控

ThreadPoolExecutor提供的執行緒池監控相關方法

方法用途
getPoolSize()獲取當前執行緒池大小
getQueue()返回工作佇列例項,通過該例項可獲取工作佇列的當前大小
getLargestPoolSize()獲取工作者執行緒數曾經達到的最大數,該數值有助於確認執行緒池的最大大小設定是否合理
getActiveCount()獲取執行緒池中當前正在執行任務的工作者執行緒數(近似值)
getTaskCount()獲取執行緒池到目前為止所接收到的任務數(近似值)
getCompletedTaskCount()獲取執行緒池到目前為止已經處理完畢的任務數(近似值)

相關文章