記一次ThreadPoolExecutor面試

ThreadPoolExecutor點滴

線程池應該也是面試繞不開的一個點,平時你們也沒少用,但其實也有一些小Tips仍是值得記錄一下。java

Constructor

public ThreadPoolExecutor(int corePoolSize,
                  int maximumPoolSize,
                  long keepAliveTime,
                  TimeUnit unit,
                  BlockingQueue<Runnable> workQueue,
                  ThreadFactory threadFactory,
                  RejectedExecutionHandler handler)
  • corePoolSize-線程池中保留的線程數量——儘管空閒(若是allowCoreThreadTimeOut被調用或者設置,在達到keepAliveTime後會銷燬)
  • maximumPoolSize-線程池中容許的最大線程數量
  • keepAliveTime-線程數量超過核心線程數,超出的空閒線程最大存活時間
  • unit-存活時間單位
  • workQueue-任務隊列,只接受經過execute方法提交的任務,最好自定義隊列爲有界隊列,不然默認是Integer.MAX_VALUE(2147483647),內存會被擠壓的任務壓爆
  • threadFactory-線程建立工廠(一個接口),默認是Executors.defaultThreadFactory(),能夠設置線程的名字(好比設置更有業務含義的名字,方便定位問題),是不是守護線程,設置優先級等
  • handler-拒絕策略handler,默認的有4個:1. 直接拒絕拋出異常;2. 交給提交線程來執行;3. 丟棄最舊提交可是沒有執行的任務;4. 直接丟棄

一個任務提交到線程池的執行流程以下:面試

  1. 當前活動線程小於核心線程數,直接新起線程運行
  2. 當前活動線程大於核心線程數,入阻塞任務隊列
  3. 當前活動線程大於核心線程數,阻塞任務隊列已滿,檢查活動線程是否達到最大線程數,沒有則新開一個線程,不然執行拒絕策略

線程池的狀態

注意是線程池的狀態而非池中處理任務線程的狀態,代碼中用一個int來存放線程池的狀態(高三位)和活動線程數(低29位)。api

  • RUNNING(-1<<29):能夠接受新任務和處理隊列中的任務
  • SHUTDOWN(0<<29):不接受新的任務,但會繼續處理隊列中的任務
  • STOP(1<<29): 不接受新的任務,也不繼續處理隊列中的任務,且會打斷正在處理中的任務
  • TIDYING(2<<29):全部的任務都terminated,worker數是0,線程轉移到該狀態會運行terminated()鉤子方法
  • TERMINATED(3<<29): terminated()執行完畢

狀態之間的轉移:ide

  • RUNNING -> SHUTDOWN, shutdown()被顯示調用
  • (RUNNING OR SHUTDOWN) -> STOP, shutdownNow()被顯示調用
  • SHUTDOWN -> TIDYING, 隊列和池都空
  • STOP -> TIDYING, 池空
  • TIDYING -> TERMINATED, terminated()執行完畢
// 主線程池控制狀態(control state)ctl,把workerCount和runState聚到到一個變量中,runStateOf/workerCountOf獲得當前線程的狀態和運行數
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int CAPACITY   = (1 << 29) - 1;
// c就是ctl
// 獲得高3位
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 獲得低29位
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

幾個容易被忽略的API

在面試中被問到過一次這樣的問題:想讓線程池在初始化時就幹活,而不是等到第一次提交任務時才建立線程,該怎麼作?線程

這個問題在實際中應該是存在的,畢竟線程的建立是有開銷的,既然建立了線程池確定是爲了用,也就不存在須要延遲建立之類的需求,不如在系統啓動時就將線程池中的線程建立好,這樣來了任務就能夠直接交由線程處理。rest

翻看線程池的doc,能夠看到有這樣兩個方法code

/**
* Starts a core thread, causing it to idly wait for work. This overrides the default policy of starting core threads only when new tasks are executed. This method will return false if all core threads have already been started.
*/
public boolean prestartCoreThread() {
    return workerCountOf(ctl.get()) < corePoolSize &&
        addWorker(null, true);
}

/**
Starts all core threads, causing them to idly wait for work. This overrides the default policy of starting core threads only when new tasks are executed.
*/
public int prestartAllCoreThreads() {
    int n = 0;
    while (addWorker(null, true))
        ++n;
    return n;
}

從源代碼能夠發現兩個方法默認的策略是隻有第一個任務到達時纔會建立線程,能夠覆蓋這兩個方法,而後在建立線程池時調用,就能夠達到目的。orm

@Override
public boolean prestartCoreThread() {
    execute(() -> System.out.println("start a core thread"));
    return true;
}

@Override
public int prestartAllCoreThreads() {
    int n = 0;
    while (++n < coreSize) {
        execute(() -> System.out.println("start a core thread"));
    }
    return n;
}

此外還有兩個hook方法:beforeExecuteafterExecute,也能夠經過覆蓋這兩個方法,在一個task執行的先後作一些事情。接口

/**
t - the thread that will run task r
r - the task that will be executed
*/
@Override
protected void beforeExecute(Thread t, Runnable r) {
    System.out.println("---before log---");
    super.beforeExecute(t, r);
}

/**
r - the runnable that has completed
t - the exception that caused termination, or null if execution completed normally
*/
@Override
protected void afterExecute(Runnable r, Throwable t) {
    super.afterExecute(r, t);
    System.out.println("---after log---");
}

END

固然線程池還有其餘不少知識點,鑑於大多數的文章都會涉及到我也不在贅述了,這兩個擴展點平時中用到的比較少,因此權當筆記和擴展一點本身的知識點,不管是面試仍是實踐,感受按照場景來都有些許用處。隊列

相關文章
相關標籤/搜索