Java線程池核心類ThreadPoolExecutor

  通常,當需要同時處理的任務比較多時,爲了避免爲每個任務開一個線程(因爲這樣會導致頻繁的線程開啓和銷燬,開銷較大),採用線程池技術來進行線程資源的複用。
  在應用中,我們通常使用Executors類提供的靜態方法來使用線程池:

ExecutorService exec0 = Executors.newCachedThreadPool(); //創建一個緩衝池,緩衝池容量大小爲Integer.MAX_VALUE
ExecutorService exec1 = Executors.newSingleThreadExecutor(); //創建容量爲1的緩衝池
ExecutorService exec2 = Executors.newFixedThreadPool(int);  //創建固定容量大小的緩衝池

  其中ExecutorService是一個接口,實現線程池的核心是一個ThreadPoolExecutor。將任務比如一個Runnable,或者一個Callable作爲參數傳入submit方法即完成任務向線程池的提交,將在線程池中自動執行該任務。返回參數可以是一個Future<?>對象。

  那麼線程池內部具體是怎麼實現的,諸如ExecutorService接口和和TheadPoolExecutor又有什麼關係呢?下面通過jdk中的源碼來學習一下。

與ThreadPoolExecutor類相關的接口和類的關係

public class ThreadPoolExecutor extends AbstractExecutorService{...};
public abstract class AbstractExecutorService implements ExecutorService {...}
public interface ExecutorService extends Executor{...}
public interface Executor {
    void execute(Runnable command);
}

  可以清楚地看到它們之間的關係,Executor 接口只聲明瞭一個方法,而ExecutorService接口在其基礎上擴展出了一些方法:

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);

    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

  其中的submit是我們比較關注的方法,也就是核心的提交任務的方法。
  AbstractExecutorService則實現了上述方法,比如如下三個版本的submit方法實現:

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

  可以看到,提交任務的方式可以是Runnable也可以是Callable,並且爲了最終可以返回參數,同意採用了將task包裝成RunnableFuture,該接口雙重繼承了Runnable接口和Future接口。然後就是execute方法的調用,execute方法的實現交給了繼承AbstractExecutorService類的子類來完成,這裏就是我們要學習的核心類:ThreadPoolExecutorService。

核心類ThreadPoolExecutorService

  其核心的構造器接口如下:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

  其中corePoolSize、maximumPoolSize分別表示核心池的大小,最大線程池的大小。keepAliveTime和空閒線程的存活時間有關,workQueue是一個阻塞隊列,用於緩存任務,後面兩個參數分別用於產生線程和拒絕服務的處理方式。

  首先,我們想搞清楚當一個任務通過線程池submit後,是怎麼被執行的而submit實際上也就是調用了execute方法,所以execute就是線程池最核心的部分。
這篇博客中介紹的比較清楚了,但是我看了下我jdk版本是1.8.0_40,發現execute的實現和文章中介紹的有差異,細看了一下基本實現思路應該還是一樣的。
在我的jdk版本中,execute方法是這樣的:

    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);
    }

  先是判斷當前線程池中的線程數和corePoolSize的大小,如果小於,則直接爲新進的任務增加一個工作線程addWorker(command, true),並返回。否則嘗試將任務添加到阻塞隊列中,後面是檢查一些異常情況,如是否外部將線程池停止isRunning(recheck)等。最後還有拒絕服務的情況。

  下面我們再來看addWorker

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            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;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        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;
    }

  這個方法用於增加工作線程,所以傳入的參數其中的任務叫firstTask, 可以看到前面一部分還用到了跳轉指令,retry點。for(;;){}一部分還沒看太懂,大致可以看出是對非正常執行情況的處理,都是一些跳轉或返回。
  後面的try塊是我們關注的重點,首先將firstTask包裝成了一個Worker類,還獲取了Worker的線程得到t,向workers這個HashSet中添加工作線程,最後調用t.start(); 來啓一個工作線程。
  我們再來看Worker類:

    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable{...}

  再來看其核心run()方法,

public void run() {
            runWorker(this);
        }

  調用了ThreadPoolExecutor的runWorker方法,

    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);
        }
    }

  到這個方法執行時,工作線程已經啓動了,進入這個方法後就不斷在任務緩衝隊列中提取任務執行,執行的過程中對該Worker加鎖,直到任務隊列爲空。

  注意到最後的finally語句塊中的processWorkerExit(w, completedAbruptly);,查看源代碼

    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

  可以看到,從workers中將移出該工作線程。還可以看到allowCoreThreadTimeOut參數對行爲的影響:該參數爲false,則對core threads在即使空閒的情況下也不會被殺死,如果爲true,則空閒的線程在一定的timeout後將被系統回收。在程序中就是若爲false,則min=corePoolSize,如果當前剩餘的工作線程大於等於該值,則不需要添加idle線程,否則需要增加一個idle線程,因爲剛剛kill了一個工作完成的線程。

  另外,還有線程池的關閉,shutdown()和shutdownNow(),前者不會立即終止線程,而是等待任務緩衝隊列中的的任務被完全執行後在結束,但是期間不再接受新的任務。後者則是立即終止所有的線程,並且清空緩衝隊列,返回尚未執行的任務。

應用

  一般使用時,我們不是直接使用ThreadPoolExecutor類,而是通過文章開頭時介紹的Executors工具類的靜態方法來獲得該類的實例對象。這就避免了繁雜的參數配置可能導致的潛在錯誤。

如何配置線程池的大小問題

  一個策略就是根據任務的類型來配置線程池的大小:
  對CPU密集型任務,我們採用Ncpu+1,對IO密集型任務,我們採用2*Ncpu。

發佈了59 篇原創文章 · 獲贊 9 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章