JUC---ThreadPoolExecutor線程池源碼解析(JDK13)

java.util.concurrent包系列文章
JUC—ThreadLocal源碼解析(JDK13)
JUC—ThreadPoolExecutor線程池源碼解析(JDK13)
JUC—各種鎖(JDK13)
JUC—原子類Atomic*.java源碼解析(JDK13)
JUC—CAS源碼解析(JDK13)
JUC—ConcurrentHashMap源碼解析(JDK13)
JUC—CopyOnWriteArrayList源碼解析(JDK13)
JUC—併發隊列源碼解析(JDK13)
JUC—多線程下控制併發流程(JDK13)
JUC—AbstractQueuedSynchronizer解析(JDK13)


線程池的組成

  • 線程池管理器 -> thread-pool
  • 工作線程(線程池中在運行的線程)-> t0,t1,t2…t9
  • 任務隊列-> blocking-queue
  • 任務接口(task)-> 隊列中的task1,task2…

在這裏插入圖片描述

  • Executor 頂層接口,只有一個 execute方法
void execute(Runnable command);
  • ExecutorService 定義了一些管理線程池,判斷線程池狀態的方法
void shutdown();優雅的關閉線程池(開始拒絕任務,並等待正在執行的線程執行完)
List<Runnable> shutdownNow();立即停止線程池,中斷正在執行的任務
boolean isShutdown();返回線程池是否停止
<T> Future<T> submit(Runnable task, T result);提交新的任務到線程池
。。。
  • ThreadPoolExecutor 真正的線程池類,Executors 的快捷創建線程池的方法內部就是new ThreadPoolExecutor()創建的線程池。

比如 newFixedThreadPool

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

在這裏插入圖片描述

  • Executors 就是一個線程池工具類,提供了一些簡單創建線程池的方法。一般不用,它的缺點後面會介紹。

在這裏插入圖片描述


ThreadPoolExecutor源碼解讀

void execute(Runnable command)
public void execute(Runnable command) {
	// null就直接拋異常
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 首先檢查是否小於核心線程數
    if (workerCountOf(c) < corePoolSize) {
    	// 如果當前工作的線程數小於核心線程數,則增加一個線程來運行任務,command就是提交進來的任務
    	// 第二個參數的意思:true表示在增加線程時判斷是否小於核心線程數,false表示判斷是否小於最大線程數
        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);
        // 如果發現當前運行的線程爲0了,則直接創建新線程執行任務
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 走到這裏說明,要麼線程數大於等於核心線程,要麼線程池停止了,要麼隊列也滿了
    // 就直接創建新的線程執行任務(當然,這個數量要小於最大線程數,如果達到了最大線程數,則執行拒絕策略)
    // addWorker()方法返回是否增加成功
    else if (!addWorker(command, false))
        reject(command);
}

以上就是線程池最核心的流程,後面會給出流程圖說明。
再來看看線程池是如何做到線程不停止,直接複用執行任務的。

boolean addWorker(Runnable firstTask, boolean core);
 private boolean addWorker(Runnable firstTask, boolean core) {
   retry:
    // 這一部分是for死循環,通過CAS操作修改當前線程數
    for (int c = ctl.get();;) {
        // Check if queue empty only if necessary.
        if (runStateAtLeast(c, SHUTDOWN)
            && (runStateAtLeast(c, STOP)
                || firstTask != null
                || workQueue.isEmpty()))
            return false;
        for (;;) {
            if (workerCountOf(c)
                >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                return false;
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateAtLeast(c, SHUTDOWN))
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
    	// 最重要的就是這裏,把提交的任務封裝成了Worker對象
        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 c = ctl.get();
                if (isRunning(c) ||
                    (runStateLessThan(c, STOP) && firstTask == null)) {
                    if (t.getState() != Thread.State.NEW)
                        throw new IllegalThreadStateException();
                    // workers是一個HashSet<Worker> ,接下來看runWorker()方法怎麼複用執行任務的
                    workers.add(w);
                    workerAdded = true;
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}
void runWorker(Worker w)
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循環,通過getTask()不斷的去隊列裏取任務來執行
        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);
                try {
                	// 最關鍵的這裏,就是執行了我們提交的每一個線程任務的run方法來執行任務的。只不過Worker 包裝了一下
                    task.run();
                    afterExecute(task, null);
                } catch (Throwable ex) {
                    afterExecute(task, ex);
                    throw ex;
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

最後看看

Runnable getTask()
private Runnable getTask() {
   boolean timedOut = false; // Did the last poll() time out?
    for (;;) {
        int c = ctl.get();
        // Check if queue empty only if necessary.
        if (runStateAtLeast(c, SHUTDOWN)
            && (runStateAtLeast(c, 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;
        }
    }
}

源碼就貼到這裏,線程池的整個流程應該比較清晰了。下面再補充一下。


ThreadPoolExecutor參數

  • corePoolSize:核心線程數,線程池完成初始化以後,線程池中並沒有任何線程,線程池會等待有任務到來時,再創建線程去執行任務。
  • maxPoolSize:最大線程數
  • keepAliveTime:空閒線程存活時間。如果線程池當前的線程數多於corePoolSize,多於的線程空閒時間超過keepAliveTime,它們就會終止
  • ThreadFactory:新的線程由ThreadFactory創建,默認使用Executors.defaultThreadFactory(),自己制定ThreadFactory,可以設置線程名,線程組,優先級,是否是守護線程等。
  • workQueue:工作隊列
    • 直接交接:SynchronousQueue,不存儲任務,任務進來直接創建線程處理,可以無限創建線程
    • 無界隊列:LinkedBlockingQueue,如果處理的速度跟不上任務提交的速度,那麼任務會越來越多。有OOM風險。
    • 有界隊列:ArrayBlockingQueue,可以設置隊列大小

添加線程的規則流程

新任務來的時候,即使其他工作線程處於空閒,也會創建一個新線程來運行新任務。 如果線程數>=corePoolSize但少於maxPoolSize。則將任務放入隊列中。如果隊列已,並且線程數小於maxPoolSize,則創建一個新線程來運行任務。如果隊列已滿並且線程數>=maxPoolSize,則拒絕該任務。

在這裏插入圖片描述

JDK默認提供的線程池(Executors工具類中)

  • newFixedThredPool:無界隊列,容易造成佔用大量內存導致OOM。
  • newSingleThreadExecutor:單線程,無界隊列,請求堆積的時候也會導致OOM。
  • newCachedThreadPool:corePoolSize設置爲0,maxPoolSize設置爲Integer.MAX_VALUE使用的SynchronousQueue。來了任務直接就給線程執行了。請求堆積的時候也會導致OOM。用完線程就全部回收銷燬。
  • newScheduledThreadPool:延遲隊列DelayedWorkQueue,支持定時以及週期性執行任務。使用於要重複運行的任務。類似於定時任務。
  • workStealingPool:1.8加入。處理子任務。子任務放到每個線程獨有的任務隊列中,某個線程執行完了,可以竊取別的線程中子任務來執行。遞歸+子任務的場景使用
    在這裏插入圖片描述

線程池的狀態

  • RUNING:接受新任務並處理排隊任務
  • SHUTDOWN:不接受新任務,但處理排隊任務
  • STOP:不接受新任務,也不處理排隊任務,並中斷正在執行的任務
  • TIDING:所有任務都已終止,workCount爲零時,線程會轉換到TIDING狀態。並將運行terminate()鉤子方法。
  • TERMINATED:terminate()運行完成

停止線程池

  • shutdown:優雅的關閉線程池。執行完隊列中的存量任務,拒絕新任務加入。
  • isShutdown:線程是否進入停止狀態(boolean)。
  • isTerminated:返回線程是否真的停止,指任務全都執行完畢。
  • awaitTermination:檢測線程是否在指定的時間內停止了。會阻塞等待指定時間。
  • shutdownNow:立刻關閉線程池 。正在執行的線程會收到interruptedException異常。返回一個沒有執行的任務List。

線程池拒絕任務

  • 當Executor關閉時,提交新任務會被拒絕。
  • 當Executor線程滿和隊列也滿了會拒絕。
拒絕策略
  • AbortPolicy:拋出異常
  • DiscardPolicy:丟棄新提交的任務
  • DiscardOldestPolicy:丟棄最老的任務
  • CallerRunsPolicy:誰提交的任務交給誰去執行(讓提交任務的線程去執行)
    在這裏插入圖片描述

線程池的線程設置爲多少比較合適

  • CPU密集型(加密、計算等):最佳線程數爲CPU核心的1-2倍
  • 耗時IO型(讀寫數據庫、文件、網絡讀寫):最佳線程數爲CPU核心的5-10倍
  • 線程數=CPU核心數*(1+平均等待時間/平均工作時間)

  • 我的公衆號:Coding摳腚
  • 一個被電焊耽誤的Java程序員。偶爾發發自己最近學到的乾貨。學習路線,經驗,技術分享。技術問題交流探討。
    Coding摳腚
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章