天天看點

萬字總結最全Java線程池ThreadPoolExecutor面試題(四)

單線程池

  • newSingleThreadExecutor建立
萬字總結最全Java線程池ThreadPoolExecutor面試題(四)

池中保持一個線程,最多也隻有一個線程,也就是說這個線程池是順序執行任務的,多餘的任務就在隊列中排隊。

固定線程池

  • newFixedThreadPool(nThreads)建立
萬字總結最全Java線程池ThreadPoolExecutor面試題(四)

池中保持nThreads個線程,最多也隻有nThreads個線程,多餘的任務也在隊列中排隊。

萬字總結最全Java線程池ThreadPoolExecutor面試題(四)
萬字總結最全Java線程池ThreadPoolExecutor面試題(四)

線程數固定且線程不逾時。

緩存線程池

  • newCachedThreadPool()建立
萬字總結最全Java線程池ThreadPoolExecutor面試題(四)

池中不保持固定數量的線程,而是按需建立,最多可建立Integer.MAX_VALUE個線程,這已大大超過目前任何os允許的線程數。

空閑的線程最多保持60s,多餘的任務在SynchronousQueue等待。

适用場景
  1. 耗時較短的任務
  2. 任務處理速度 > 任務送出速度 ,這樣才能保證不會不斷建立新的程序,避免記憶體被占滿。

線程池中的線程是被線程池緩存了的,即:

線程沒有任務執行時,便處于空閑狀态,處于空閑狀态的線程并不會被立即銷毀(會被緩存),隻有當空閑時間超出一段時間(預設60s)後,線程池才會銷毀該線程(相當于清除過時緩存)

新任務到達後,線程池首先會讓被緩存住的線程(空閑狀态)去執行任務,若無可用線程(無空閑線程),便會建立新的線程。

為什麼使用SynchronousQueue()?

因為單線程池和固定線程池中,線程數量有限,是以送出的任務需要在LinkedBlockingQueue隊列中等待空閑線程。

而緩存線程池中,線程數量幾乎無限(上限為Integer.MAX_VALUE),是以送出的任務隻需要在SynchronousQueue隊列中同步移交給空餘線程即可。

固定排程線程池

  • newScheduledThreadPool(n)建立
萬字總結最全Java線程池ThreadPoolExecutor面試題(四)
萬字總結最全Java線程池ThreadPoolExecutor面試題(四)

池中保持n個線程,多餘的任務在DelayedWorkQueue中等待。

有一項技術可以緩解執行時間較長任務造成的影響,即限定任務等待資源的時間,而不要無限的等待.

先看第一個例子,測試單線程池、固定線程池和緩存線程池(注意增加和取消注釋):

public class ThreadPoolExam {
    public static void main(String[] args) {
        //first test for singleThreadPool
        ExecutorService pool = Executors.newSingleThreadExecutor();
        //second test for fixedThreadPool
//        ExecutorService pool = Executors.newFixedThreadPool(2);
        //third test for cachedThreadPool
//        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            pool.execute(new TaskInPool(i));
        }
        pool.shutdown();
    }
}

class TaskInPool implements Runnable {
    private final int id;

    TaskInPool(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 5; i++) {
                System.out.println("TaskInPool-["+id+"] is running phase-"+i);
                TimeUnit.SECONDS.sleep(1);
            }
            System.out.println("TaskInPool-["+id+"] is over");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
      

如圖為排查底層公共緩存調用出錯時的截圖

  • 有意義的線程命名

綠色框采用自定義的線程工廠,明顯比藍色框預設的線程工廠建立的線程名稱擁有更多的額外資訊:如調用來源、線程的業務含義,有助于快速定位到死鎖、StackOverflowError 等問題。

Executors類提供的一些快捷聲明線程池的方法雖然簡單,但隐藏了線程池的參數細節。是以,使用線程池時,我們一定要根據場景和需求配置合理的線程數、任務隊列、拒絕政策、線程回收政策,并對線程進行明确的命名友善排查問題。