Java-線程池源碼簡析

一、線程池介紹

Java5開始,在util下提供了一個包,叫做JUC(java.util.concurrent),裏面提供了關於多線程、併發的一些工具包。例如鎖、多線程等工具都在這個包中。

我們知道一個線程的創建、銷燬過程是會消耗系統性能的,需要使用cpu,佔用內存,當頻繁大量的創建銷燬線程,這個消耗累積到會影響系統性能,爲了解決這一類問題,出現了“池化”的概念,“池化”意爲將資源放進一個池子內,需要的時候就從池中取,不用的時候就返還池中以便其它複用,實踐的例子很多,例如數據庫連接池、緩衝池以及本次講解的線程池等,中心思想就是複用這些連接,避免頻繁的創建銷燬,提高性能。

juc中的線程池的類關係如下圖:

Executor.class是頂層接口類,只定義了一個方法:execute()方法,入參爲Runnable接口;

ExecutorService.class是繼承Executor的接口,加入了一些方法submit()、shutdown()、shutdownNow()等方法的定義;

AbstractExecutorService抽象類實現了ExecutorService部分接口,比如submit()等方法;

ScheduleExecutorService接口繼承ExecutorService,顧名思義,增加了一些計劃執行函數,實現週期行執行任務的功能;

ThreadPoolExecutor類就是我們常用的線程池工具類了,它繼承於抽象類AbstractExecutorService,實現了線程池的任務、線程以及隊列等功能,接下來我們詳細介紹ThreadPoolExecutor的功能實現。

二、線程池源碼

如何使用線程池?假設我們有如下圖線程池實例代碼,可以看到大致創建線程池並且啓動的大概邏輯爲創建,執行:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ExecutorTest {
    public static void main(String[] args) {
        class MyRunnable implements Runnable {
            @Override
            public void run() {
                System.out.println("Running----");
            }
        }
        //創建線程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5,
                10,
                10,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(10), new ThreadPoolExecutor.DiscardPolicy());
        //加入執行任務
        executor.submit(new MyRunnable());
    }
}

線程池構造參數含義?可以看到創建一個線程池,構造方法中需要傳入很多參數,每個參數都有很重要的含義,下表列出了每個參數的含義:

參數名稱 含義 備註

int corePoolSize

核心線程 線程池核心線程數,包括空閒線程

int maximumPoolSize

最大線程數 線程池所允許的最大線程數量

long keepAliveTime

線程存活時間 線程空閒時間超過該值則銷燬(對核心線程起作用需配置參數)

TimeUnit unit

時間單位 線程空閒存活時間單位

BlockingQueue<Runnable> workQueue

阻塞隊列 當核心線程用盡,將新任務加入隊列

ThreadFactory threadFactory

線程工廠 創建線程的工廠

RejectedExecutionHandler handler

拒絕策略 當隊列滿時,根據該策略處理新到達的任務

注:boolean allowCoreThreadTimeOut決定keepAliveTime是否對核心線程有作用;

ThreadPoolExecutor提供了四種拒絕策略

  • 1.AbortPolicy:默認策略,丟棄任務,並且拋出RejectedExecutionException;
  • 2.DiscardPolicy:丟棄任務,什麼都不做;
  • 3.DiscardOldestPolicy:丟棄老任務,執行該新任務;
  • 4.CallerRunsPolicy:調用者線程執行該任務;

注:也可以自定義拒絕策略,實現RejectedExecutionHandler接口。

ThreadPoolExecutor提供四種基礎阻塞隊列:頂層是BlockingQueue接口

  • 1.ArrayBlockingQueue:無界阻塞隊列,底層爲數組;
  • 2.LinkedBlockingQueue:無界或有界阻塞隊列,底層爲鏈表;
  • 3.SynchronousQueue:不存儲元素,一個線程執行元素插入,需等待另一個線程執行移除元素,否則阻塞插入操作;
  • 4.PriorityBlockingQueue:無界的,帶有優先級的阻塞隊列;

注:隊列一般需要設置上限,需要注意無界隊列,如果過多的任務堆積於隊列中,有oom風險。

ThreadPoolExecutor線程池狀態流轉:

線程池中基本成員參數?在線程池中維護了一些參數,例如狀態、線程組以及鎖等信息,如下圖所示:

    //32位,前3位代表線程池狀態,後29位代表線程池數量    
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //線程數量位32 - 3 = 29
    private static final int COUNT_BITS = Integer.SIZE - 3;
    //線程數容量000 11111111111111111111111111111
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    //各個運行狀態標誌(值:running<~<terminated)
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

    //獲取ctl、線程池狀態、線程數
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }

    //阻塞隊列,任務容器
    private final BlockingQueue<Runnable> workQueue;
    //鎖,增加線程數、完成任務數等使用
    private final ReentrantLock mainLock = new ReentrantLock();
    //線程的容器
    private final HashSet<Worker> workers = new HashSet<Worker>();
    //鎖的條件功能
    private final Condition termination = mainLock.newCondition();
   
    //線程數量峯值
    private int largestPoolSize;
    //完成的任務數量
    private long completedTaskCount;
    //創建線程的工廠
    private volatile ThreadFactory threadFactory;
    //拒絕策略句柄
    private volatile RejectedExecutionHandler handler;
    //空閒線程存活時間
    private volatile long keepAliveTime;
    //是否主線程也有過期時間
    private volatile boolean allowCoreThreadTimeOut;
    //核心線程數量
    private volatile int corePoolSize;
    //最大線程數量
    private volatile int maximumPoolSize;

線程池啓動過程?當我們創建一個線程池後,添加並執行任務時:

1.首先判斷當前線程的數量是否小於核心線程數量,如果小於,則直接創建核心線程並執行此任務,如果大於等於,則嘗試把此任務加入到阻塞隊列;

2.如果加入阻塞隊列成功,則等待空閒線程來阻塞隊列拉取任務並執行;

3.如果加入阻塞隊列失敗,則說明隊列已經滿了,這時候需要判斷當前線程數量是否小於最大線程數,如果小於,則創建非核心線程並執行該任務;

4.如果大於等於最大線程數,則使用拒絕策略處理該任務;

線程池的大概執行邏輯如上步驟,接下來我們看代碼。我們知道,線程池的入口執行方法是submit()以及execute(),前者實際上也是調用的後者,因此我們從execute()方法入口開始介紹執行過程:

    public void execute(Runnable command) {
        if (command == null)//傳入的任務爲null,直接拋出空指針異常
            throw new NullPointerException();
        int c = ctl.get();//獲取ctl,拿到線程池狀態、線程數量
        //1.獲取線程數量,和核心線程數量比較
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))//小於核心線程,則創建核心線程,並且把該任務加入到該線程
                return;
            c = ctl.get();//創建核心線程失敗(可能線程池非運行狀態、核心線程剛滿),重新獲取ctl
        }
        //2.如果超出核心線程數,嘗試加入隊列
        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);
        }
        //3.非運行或者隊列滿,則創建非核心線程執行該任務
        else if (!addWorker(command, false))
            reject(command);//超出最大線程數量或非運行狀態,拒絕策略處理
    }

 可以看出主要判斷邏輯爲:1.核心線程是否滿;2.隊列是否滿;3.是否超過最大線程數;

上述過程的流程圖如下圖所示:

 上述過程中,一個重要的方法addWorker()是新建一個worker,並把任務放入該worker中執行,下面我們分析該方法的執行過程:

private boolean addWorker(Runnable firstTask, boolean core) {
        //1.增加線程數量
        retry://goto 標識
        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()))//如果線程池非運行狀態,且是停止狀態、任務爲空、隊列爲空,則返回false,添加失敗
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))//規則校驗線程數量
                    return false;
                if (compareAndIncrementWorkerCount(c))//CAS增加線程數量
                    break retry;//增加線程數量成功,跳出外循環
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)//增加線程數量失敗,線程池狀態改變則開始外循環,狀態未變則開始內循環
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
        //2.新建worker,執行線程任務
        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;
    }

 當在addWorker()方法中添加成功後,執行了start()函數,會去調用Worker類的run()方法,run()方法調用runWoker()方法,執行過程如下:

    //Woker方法重寫的run()方法
    public void run() {
        runWorker(this);
    }

    //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 pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                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);
        }
    }

線程池如何停止?在上方線程池狀態流轉圖中可以看到,線程池可以通過shutdown()、shutdownNow()方法使線程池到達shutdown、stop停止狀態,在運二者區別在於對於隊列以及執行中的任務的處理方式,前者會等待隊列及正在運行的任務完成纔會執行退出邏輯,後者會終止正在運行的任務,剔除並返回所有隊列中的任務,是比較粗暴的,下面介紹兩種結束方式:

shutdownNow()方法執行邏輯?設置線程池狀態爲STOP,中斷所有未中斷線程,移除所有任務並返回,

    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);//CAS設置狀態爲STOP
            interruptWorkers();//中斷所有線程
            tasks = drainQueue();//移除所有任務,並返回
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

中斷方法是比較粗暴的:

    private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }

    void interruptIfStarted() {
        Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
            }
        }
    }

對於隊列中未執行的任務,直接拋棄並返回,那對於正在執行的任務是如何處理呢?文章中部圖runWoker()方法中可以看到循環獲取當前線程和阻塞隊列裏的線程,然後加鎖、執行、結束放鎖。運行中的線程改變了中斷標誌,如果處於阻塞狀態(IO阻塞)則會拋異常,然後結束本線程,如果是正常執行線程,則執行完畢後退出。

如果在getTask()方法返回null,則線程完畢,方法如下,可以看到開始會判斷線程池的狀態,shutdownNow()此時已經將下線程池標註爲STOP狀態了。

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

shutdown()方法執行邏輯?相比於shutdownNow()方法,則更加優雅一些,代碼如下:

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

中斷方法如下,可以看到有一個加鎖操作,而我們反過頭來看到,runWorker()方法,執行中的方法先獲取到鎖了,因此執行中的任務,這裏是無法終止的。

    private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {//非中斷,並且嘗試加鎖成功
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

三、固定線程池

juc包中提供了一個類Executors,它提供了一些特殊固定功能的線程池,主要包括四種:

1.newSingleThreadPool:單一線程池,有且僅有一個線程;LinkedBlockingQueue是無界阻塞隊列,隊列可存放的大小爲Integer.MAX_VALUE

2.newFixedThreadPool:固定數量線程的線程池,LinkedBlockingQueue是無界阻塞隊列,隊列可存放的大小爲Integer.MAX_VALUE;

3.newCachedThreadPool:緩衝線程池,核心線程0,最大線程池數量是Integer.MAX_VALUE,空閒時間60s,使用SynchronousQueue,該隊列不存儲值;

4.newScheduledThreadPool:定時調度的線程池;

四、注意事項

1.合理配置線程數

線程數的多少直接關乎到線程池的性能效率以及對系統的影響,首先需要分析我們的業務是IO密集型還是CPU密集型,根據實際業務情況,配置合理的線程數。

IO密集型業務主要是阻塞進行IO操作,比較耗時,因此需要配置多的線程數,彌補阻塞的時間,一般配置爲CPU核數*2;

CPU密集型業務是進行大量的運算,多核的CPU可以提高計算速度,少量配置線程數,這樣增加多核計算機率,一般配置CPU核數+1的線程數量;

2.優雅關閉線程池

線程池中提供了兩種停止線程池的方法:shutdown()、shutdownNow(),兩者執行後的線程池狀態在上面圖中已經描述了,爲了保證任務合理的關閉,我們應該選取第一種方式關閉,停止接收外部任務,執行完阻塞隊列和正在執行的任務,然後,在通過方法awaitTermination()阻塞主線程,等待子線程任務完成後,優雅的關閉。

3.謹慎使用Executors提供的特殊線程池

Executors提供的幾種特殊線程池,雖然省去了傳入部分參數的麻煩,但是裏邊是有一部分的風險的。

單一或者固定數量的線程池,使用幾乎無界的阻塞隊列,如果任務執行很慢,但是任務很多,這個隊列會急劇增加,可能會佔用大量內存,並且因爲在使用無法剔除,導致oom。

緩衝線程池,隊列不會存儲任務,當任務量急劇增多,會瞬間創建大量的線程,這個也是很危險的。雖然我們前面已經根據業務評估使用不同的線程池,但是這些風險還是存在的,因此估計使用自定義的線程池。

五、資源地址

官網:http://www.java.com

文檔:《Thinking in java》jdk1.8版本源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章