聊聊面試中的 Java 執行緒池

Java極客技術發表於2019-07-23

​背景

關於 Java 的執行緒池我想大家肯定不會陌生,在工作中或者自己平時的學習中多多少少都會用到,那你真的有了解過底層的實現原理嗎?還是說只停留在用的階段呢?而且關於 Java 執行緒池也是在面試中的一個高頻的面試題,就像 HashMap 的實現原理一樣,基本上面試必問,估計都已經被問爛大街了。

題外話:HashMap 的實現原理真的已經被問爛了,在我自身的多次面試中都不知道被問了幾遍了,有的時候想想很奇怪,為什麼這個被問的爛大街的問題還是會一直被問呢?但是從面試官的角度來想一下,如果一個被問的都爛大街的問題你都不好好準備對待,那怎麼能好好的對待工作呢(個人愚見)。

常用的幾種執行緒池

我們先來看下常用的幾種執行緒池的建立方式,以及底層採用的實現原理

單個執行緒: Executors.newSingleThreadExecutor();

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

快取執行緒: Executors.newCachedThreadPool();

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

固定執行緒Executors.newFixedThreadPool(2);

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

定時執行緒: Executors.newScheduledThreadPool(3);(父類中)

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

核心 ThreadPoolExecutor

通過上面的幾個執行緒池的底層實現,我們可以發現底層都是通過 ThreadPoolExecutor 類來實現的,只是引數不一樣,那我們就很有必要來看一下ThreadPoolExecutor 這個類了

public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue,                              ThreadFactory threadFactory,                              RejectedExecutionHandler handler) {        if (corePoolSize < 0 ||            maximumPoolSize <= 0 ||            maximumPoolSize < corePoolSize ||            keepAliveTime < 0)            throw new IllegalArgumentException();        if (workQueue == null || threadFactory == null || handler == null)            throw new NullPointerException();        this.corePoolSize = corePoolSize;        this.maximumPoolSize = maximumPoolSize;        this.workQueue = workQueue;        this.keepAliveTime = unit.toNanos(keepAliveTime);        this.threadFactory = threadFactory;        this.handler = handler;    }

通過 JDK 的原始碼我們可以看到 ThreadPoolExecutor 在 Java 的 concurrent 包下面,並且有四個構造方法,下面依次介紹下各個引數的含義:

•corePoolSize: 核心執行緒數的大小•maximumPoolSize: 執行緒池中允許的最大執行緒數•keepAliveTime: 空閒執行緒允許的最大的存活時間•unit: 存活時間的單位•workQueue: 阻塞任務佇列•threadFactory: 執行緒工廠用來建立執行緒•handler: 拒絕策略,針對當佇列滿了時新來任務的處理方式

通過上面引數的分析,我們可以知道,單個執行緒的執行緒池就是執行緒池中只有一個執行緒負責任務,所以 corePoolSize 和 maximumPoolSize 的數值都是為 1;當這個執行緒出現任何異常後,執行緒池會自動建立一個執行緒,始終保持執行緒池中有且只有一個存活的執行緒。而且其他執行緒池也只是引數的設定不一樣而已。 我們還需要知道幾個常見的執行緒池類和介面的關係,以及一些方法,如下圖

ThreadPoolExecutor 繼承 AbstractExecutorServiceAbstractExecutorService 實現 ExecutorService, ExecutorService 繼承 Executor

原始碼分析

根據原始碼可以發現整個執行緒池大致分為 3 個部分,1. 是建立 worker 執行緒,2. 新增任務到 workQueue; 3.worker 執行緒執行具體任務

建立 worker 執行緒,現在我們來看下核心的 execute(Runnable command) 方法,如果工作執行緒小於指定的核心執行緒數時會嘗試去建立新的執行緒,

public void execute(Runnable command) {    if (command == null)        throw new NullPointerException();    int c = ctl.get();    //如果工作執行緒比核心執行緒數少,則建立新執行緒    if (workerCountOf(c) < corePoolSize) {        if (addWorker(command, true))            return;        c = ctl.get();    }    if (isRunning(c) && workQueue.offer(command)) {        int recheck = ctl.get();        if (! isRunning(recheck) && remove(command))            reject(command);        else if (workerCountOf(recheck) == 0)            addWorker(null, false);    }    else if (!addWorker(command, false))        reject(command);}

再看下addWorker(Runnable firstTask, boolean core) 方法

private boolean addWorker(Runnable firstTask, boolean core) {    retry:    for (;;) {        int c = ctl.get();        int rs = runStateOf(c);        if (rs >= SHUTDOWN &&            ! (rs == SHUTDOWN &&               firstTask == null &&               ! workQueue.isEmpty()))            return false;        for (;;) {            int wc = workerCountOf(c);            if (wc >= CAPACITY ||                wc >= (core ? corePoolSize : maximumPoolSize))                return false;            if (compareAndIncrementWorkerCount(c))                break retry;            c = ctl.get();  // Re-read ctl            if (runStateOf(c) != rs)                continue retry;        }    }    boolean workerStarted = false;    boolean workerAdded = false;    Worker w = null;    try {        w = new Worker(firstTask);        final Thread t = w.thread;        if (t != null) {            final ReentrantLock mainLock = this.mainLock;            mainLock.lock();            try {                // Recheck while holding lock.                // Back out on ThreadFactory failure or if                // shut down before lock acquired.                int rs = runStateOf(ctl.get());                if (rs < SHUTDOWN ||                    (rs == SHUTDOWN && firstTask == null)) {                    if (t.isAlive()) // precheck that t is startable                        throw new IllegalThreadStateException();                    workers.add(w);                    int s = workers.size();                    if (s > largestPoolSize)                        largestPoolSize = s;                    workerAdded = true;                }            } finally {                mainLock.unlock();            }            if (workerAdded) {                t.start();                workerStarted = true;            }        }    } finally {        if (! workerStarted)            addWorkerFailed(w);    }    return workerStarted;}

新增任務到 workQueue,這個阻塞佇列內部的方法

public boolean offer(E e) {    if (e == null) throw new NullPointerException();    final AtomicInteger count = this.count;    if (count.get() == capacity)        return false;    int c = -1;    Node<E> node = new Node<E>(e);    final ReentrantLock putLock = this.putLock;    putLock.lock();    try {        if (count.get() < capacity) {            enqueue(node);            c = count.getAndIncrement();            if (c + 1 < capacity)                notFull.signal();        }    } finally {        putLock.unlock();    }    if (c == 0)        signalNotEmpty();    return c >= 0;}

worker 執行緒執行具體任務,阻塞或者超時去獲取佇列中的任務,進行執行

final void runWorker(Worker w) {    Thread wt = Thread.currentThread();    Runnable task = w.firstTask;    w.firstTask = null;    w.unlock(); // allow interrupts    boolean completedAbruptly = true;    try {        //阻塞迴圈獲取任務        while (task != null || (task = getTask()) != null) {            w.lock();            if ((runStateAtLeast(ctl.get(), STOP) ||                 (Thread.interrupted() &&                  runStateAtLeast(ctl.get(), STOP))) &&                !wt.isInterrupted())                wt.interrupt();            try {                beforeExecute(wt, task);                Throwable thrown = null;                try {                    task.run();                } catch (RuntimeException x) {                    thrown = x; throw x;                } catch (Error x) {                    thrown = x; throw x;                } catch (Throwable x) {                    thrown = x; throw new Error(x);                } finally {                    afterExecute(task, thrown);                }            } finally {                task = null;                w.completedTasks++;                w.unlock();            }        }        completedAbruptly = false;    } finally {        processWorkerExit(w, completedAbruptly);    }}private Runnable getTask() {    boolean timedOut = false; // Did the last poll() time out?    for (;;) {        int c = ctl.get();        int rs = runStateOf(c);        // Check if queue empty only if necessary.        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {            decrementWorkerCount();            return null;        }        int wc = workerCountOf(c);        // Are workers subject to culling?        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;        if ((wc > maximumPoolSize || (timed && timedOut))            && (wc > 1 || workQueue.isEmpty())) {            if (compareAndDecrementWorkerCount(c))                return null;            continue;        }        try {            Runnable r = timed ?                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :                workQueue.take();            if (r != null)                return r;            timedOut = true;        } catch (InterruptedException retry) {            timedOut = false;        }    }}

在剛剛建立執行緒池的時候,內部執行緒的數量是 0,當首個任務進行新增的時候,會根據引數的配置進行執行緒的建立,並隨著任務數的增加,會逐漸建立新的執行緒直到不能建立新的執行緒為止。不能建立新的執行緒後,會將來的任務存放到阻塞佇列中,讓空閒的執行緒去處理。當沒有空閒執行緒並且佇列滿了時候就會採用拒絕策略去丟棄或者其他策略來處理。 拒絕策略主要有四種,不同的拒絕策略有不同的使用場景,需要根據情況決定使用。

CallerRunsPolicy : 呼叫執行緒處理任務•AbortPolicy : 丟擲異常•DiscardPolicy : 直接丟棄•DiscardOldestPolicy : 丟棄佇列中最老的任務,執行新任務

小結

執行緒池在工作中的使用必不可少,如何優雅的使用執行緒池能很大程度的提升效能和效率。根據實際的應用場景,配置合適的執行緒池引數可以很大的提升專案的效能,也可以充分利用伺服器的效能。

 


 


 

Java 極客技術公眾號,是由一群熱愛 Java 開發的技術人組建成立,專注分享原創、高質量的 Java 文章。如果您覺得我們的文章還不錯,請幫忙讚賞、在看、轉發支援,鼓勵我們分享出更好的文章。

關注公眾號,大家可以在公眾號後臺回覆“部落格園”,免費獲得作者 Java 知識體系/面試必看資料。

 

相關文章