jdk1.8 執行緒池部分原始碼分析

pc859107393發表於2018-09-19

首先是老規矩,推薦一下我的企鵝交流群:

有興趣交流springboot進行快速開發的同學可以加一下下面的企鵝群。

行走的java全棧

普通執行緒

  • 1.實現:繼承Thread或者實現Runnable介面
    • 1.繼承Thread,僅僅只能單繼承
    • 2.實現Runnable介面(可實現內部資源共享),介面可以多實現
    • 3.經典問題:視窗賣票
  • 2.例項化物件
  • 3.執行任務
  • 4.銷燬執行緒回收資源
思考:

當多個資源需要開啟執行緒來處理的時候,我們怎麼辦?是否一直在重複下面的流程:

    create -> run -> destroy
複製程式碼

我們知道計算機的每次執行都是需要大量的資源消耗,5個執行緒的操作可能沒有影響,5w個呢? 五萬次建立和銷燬才有僅僅五萬次的執行嗎?執行任務可能花費了大量的時間來處理這些建立和銷燬。

執行緒池

特點
  • 1.解決處理器單元內多個執行緒的執行問題
  • 2.減少處理器單元閒置時間
  • 3.增加了處理器單元工作時間內的吞吐能力(為什麼這麼說?我們減少了多個任務每次執行緒的建立和銷燬浪費,提高了任務執行效率)
組成
  • 1.執行緒池管理器(ThreadPool):負責建立、管理、銷燬執行緒池,以及新增任務
  • 2.工作執行緒(PoolWorker):無任務則等待,可迴圈、重複執行任務
  • 3.任務介面(Task):每個任務必須實現介面,工作執行緒負責排程任務的執行,規定了任務的入口,以及任務完成後的收尾工作以及任務執行狀態等等
  • 4.任務佇列(TaskQueue):存放沒有處理的任務,提供任務緩衝機制

eg:超市結賬:收營員服務組,單個收營員,收銀工作,等待被收銀的人群

JDK執行緒池類:java.util.concurrent.Executors和JDK執行緒池執行器介面:java.util.concurrent.Executor

在Executors中,jdk提供了一下相關的執行緒池,如下:

靜態方法 建立的執行緒池型別 返回值的實際實現
newFixedThreadPool(int) 固定執行緒池 ThreadPoolExecutor
newWorkStealingPool() 處理器核心數的並行執行緒池 ForkJoinPool
newSingleThreadExecutor() 一個執行緒的單獨執行緒池 FinalizableDelegatedExecutorService
newCachedThreadPool() 快取執行緒池 ThreadPoolExecutor
newSingleThreadScheduledExecutor() 單獨執行緒定時執行緒池 DelegatedScheduledExecutorService
newScheduledThreadPool(int) 定時執行緒池 ScheduledThreadPoolExecutor

newSingleThreadExecutor() 一個執行緒的執行緒池

為什麼這裡我要拿一個執行緒的執行緒池來說明呢?其實我們把簡單的搞定複雜的也是演變過來的。先上碼:

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

public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
            0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(),
                    threadFactory));
}
複製程式碼

我們可以看到上面方法的返回值都是ExecutorService,但實際上例項化的是FinalizableDelegatedExecutorService,我們進去看看原始碼,如下:

static class FinalizableDelegatedExecutorService extends DelegatedExecutorService {
    //構造方法
    FinalizableDelegatedExecutorService(ExecutorService executor) {
        super(executor);
    }
    
    //物件銷燬的時候呼叫
    protected void finalize() {
        super.shutdown();
    }
}
複製程式碼

上面的程式碼我們可以明顯的看到FinalizableDelegatedExecutorService僅僅是對DelegatedExecutorService的封裝,唯一實現的就是在物件銷燬的時候將ExecutorService結束。

到這裡我們就應該返回來分析DelegatedExecutorService,以及上面的方法中的具體程式碼。

我們看看預設的單執行緒執行緒池的實現,如下:

new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
            0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>()));
//此處的程式碼實現了一個ExecutorService,分別有幾個引數?何解?

//
public class ThreadPoolExecutor extends AbstractExecutorService {
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
}
//我們可以看到幾個引數的字面意思分別是:
//corePoolSize 核心執行緒數量,包括空閒執行緒
//maximumPoolSize 最大執行緒數量
//keepAliveTime 保持活躍時間(參照後續原始碼,這裡應該是:當執行緒數大於核心時,此為終止前多餘的空閒執行緒等待新任務的最長時間)
//unit keepAliveTime 引數的時間單位
//workQueue 執行前用於保持任務的佇列。此佇列僅保持由 execute方法提交的 Runnable任務
//Executors.defaultThreadFactory() 預設執行緒工廠
//defaultHandler 超出執行緒和任務佇列的任務的處理程式,實現為:new AbortPolicy(),當然這裡預設是沒有處理的,需要我們手動實現

//這裡,我們接著看預設的執行緒工廠,畢竟執行緒池核心是需要執行緒來執行任務,所以此處先看執行緒來源。
static class DefaultThreadFactory implements ThreadFactory {
    //池數量,指定原子操作
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    //執行緒組
    private final ThreadGroup group;
    //執行緒數量,指定原子操作
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    //執行緒名稱字首
    private final String namePrefix;

    DefaultThreadFactory() {
        //獲取系統安全管理器
        SecurityManager s = System.getSecurityManager();
        //建立執行緒組,由是否獲取系統安全管理器決定
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        //構造執行緒名稱
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }
    
    //建立執行緒
    public Thread newThread(Runnable r) {
        //將執行緒組、Runnable介面(執行緒實際執行程式碼塊)、執行緒名、執行緒所需要的堆疊大小為0
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        //如果為守護執行緒,取消守護狀態,必須線上程執行前呼叫這個setDaemon方法
        if (t.isDaemon())
            t.setDaemon(false);
        //預設任務優先順序,值為5
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}
//上面的預設執行緒工廠,提供給了我們一個非守護執行緒的執行緒,由原子操作保證執行緒唯一,任務優先順序預設(最低1,最高10,預設5,此處優先順序為5)

複製程式碼

看了上面這些我們可以總結一下:單執行緒執行緒池,預設只有一個執行緒和一個執行緒池,等待新任務時間為0,新增了原子操作來繫結執行緒。

是不是到這裡就完了? 當然沒有,我們現在需要看看更加具體的ThreadPoolExecutor,才能更加深入明白執行緒池。

public class ThreadPoolExecutor extends AbstractExecutorService {
    /**
    *所有的構造方法均指向這裡,所以我們看一下這個就足夠
    */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        //引數檢查,說明執行緒池不能執行緒=0,也不能最大執行緒數量不大於0切最大執行緒數量不能少於核心執行緒數量,等待任務最長時間不能小於0
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        //等待任務佇列、執行緒工廠、超任務佇列的處理程式
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        //上面的判斷,可以看做是一種防禦式程式設計,所有的問題預先處理,後續無需考慮類似問題
        //構造執行緒池相關設定閾值
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
}

//到了這裡其實我們不必先追究具體的實現,還是先看看AbstractExecutorService吧。

//抽象的執行服務
public abstract class AbstractExecutorService implements ExecutorService {
    
    //執行方法
    private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
                              boolean timed, long nanos)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (tasks == null)
            throw new NullPointerException();
        //獲取任務數量
        int ntasks = tasks.size();
        if (ntasks == 0)
            throw new IllegalArgumentException();
        //任務集合
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
        //執行完成服務
        ExecutorCompletionService<T> ecs =
            new ExecutorCompletionService<T>(this);

        try {
            // 記錄異常
            ExecutionException ee = null;
            
            //超時時間線
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            //使用迭代器獲取任務
            Iterator<? extends Callable<T>> it = tasks.iterator();

            // 確定開始一項任務
            futures.add(ecs.submit(it.next()));
            //任務數量減少
            --ntasks;
            //正在執行任務標誌
            int active = 1;
            
            //迴圈執行任務
            for (;;) {
                //獲取任務佇列中第一個任務
                Future<T> f = ecs.poll();
                //任務為空,如果還有任務則執行任務(任務數量減1,提交任務到執行佇列,正在執行任務數量+1)
                //正在執行任務數為0,說明任務執行完畢,中斷任務迴圈
                //若有超時檢查,則執行超時檢查機制
                //上述情況都不滿足,則取出任務佇列頭,並將其從佇列移除
                if (f == null) {
                    if (ntasks > 0) {
                        --ntasks;
                        futures.add(ecs.submit(it.next()));
                        ++active;
                    }
                    else if (active == 0)
                        break;
                    else if (timed) {
                        f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
                        if (f == null)
                            throw new TimeoutException();
                        nanos = deadline - System.nanoTime();
                    }
                    else
                        f = ecs.take();
                }
                
                //任務不為空
                if (f != null) {
                    //正在執行標誌-1
                    --active;
                    try {
                        //返回執行結果
                        return f.get();
                    } catch (ExecutionException eex) {
                        ee = eex;
                    } catch (RuntimeException rex) {
                        ee = new ExecutionException(rex);
                    }
                }
            }

            if (ee == null)
                ee = new ExecutionException();
            throw ee;

        } finally {
            //取消所有任務
            for (int i = 0, size = futures.size(); i < size; i++)
                futures.get(i).cancel(true);
        }
    }
    
    //執行方法
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                         long timeout, TimeUnit unit)
        throws InterruptedException {
        if (tasks == null)
            throw new NullPointerException();
        long nanos = unit.toNanos(timeout);
        //和上面類似,這裡也是建立任務佇列
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
        boolean done = false;
        //迭代進行任務執行
        try {
            //建立任務,並新增到任務佇列
            for (Callable<T> t : tasks)
                futures.add(newTaskFor(t));
            //設定超時時間標記
            final long deadline = System.nanoTime() + nanos;
            final int size = futures.size();

            //在執行器沒有多少多並行性的情況下,交替執行時間檢查和呼叫。
            for (int i = 0; i < size; i++) {
                execute((Runnable)futures.get(i));
                nanos = deadline - System.nanoTime();
                //任務超時,返回任務佇列
                if (nanos <= 0L)
                    return futures;
            }
            
            //遍歷任務並返回任務執行結果
            for (int i = 0; i < size; i++) {
                Future<T> f = futures.get(i);
                if (!f.isDone()) {
                    //超時
                    if (nanos <= 0L)
                        return futures;
                    try {
                        //給定執行時間等待任務完成並返回結果
                        f.get(nanos, TimeUnit.NANOSECONDS);
                    } catch (CancellationException ignore) {
                    } catch (ExecutionException ignore) {
                    } catch (TimeoutException toe) {
                        return futures;
                    }
                    nanos = deadline - System.nanoTime();
                }
            }
            done = true;
            return futures;
        } finally {
            //未完成則取消執行
            if (!done)
                for (int i = 0, size = futures.size(); i < size; i++)
                    futures.get(i).cancel(true);
        }
    }
    
    /**
    *建立任務佇列
    */
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }
    
    /**
    * 提交任務到執行佇列
    */
    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }
}
複製程式碼

通過上面的程式碼我們已經基本瞭解了一個執行緒池是如何建立任務佇列並執行任務的,所以在這裡我們只需要關注一些關鍵的ThreadPoolExecutor的方法就能瞭解執行緒池是如何工作的,並且對應的幾種模式的執行緒池都可以推匯出來。

首先在這次看原始碼之前我們要胡亂思索一番,整理一下執行緒池的執行大概流程:

執行緒池執行流程

我們前面簡單的說過幾個ThreadPoolExecutor的主要引數,我們下面再仔細總結一下:

public class ThreadPoolExecutor extends AbstractExecutorService {
    
    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;
    }
}
複製程式碼
  • corePoolSize:該執行緒池中核心執行緒數最大值

核心執行緒:執行緒池新建執行緒的時候,如果當前執行緒總數小於corePoolSize,則新建的是核心執行緒,如果超過corePoolSize,則新建的執行緒不是核心執行緒。核心執行緒預設情況下會一直存活線上程池中,即使這個核心執行緒啥也不幹(閒置狀態)。如果指定ThreadPoolExecutor的allowCoreThreadTimeOut這個屬性為true,那麼核心執行緒如果不幹活(閒置狀態)的話,超過一定時間(時長下面引數決定),就會被銷燬掉。

  • maximumPoolSize: 該執行緒池中執行緒總數最大值

執行緒總數 = 核心執行緒數 + 非核心執行緒數。

  • keepAliveTime:該執行緒池中非核心執行緒閒置超時時長

一個非核心執行緒,如果不幹活(閒置狀態)的時長超過這個引數所設定的時長,就會被銷燬掉,如果設定allowCoreThreadTimeOut = true,則會作用於核心執行緒。

  • unit:keepAliveTime的單位

TimeUnit是一個列舉型別,其包括: NANOSECONDS : 1微毫秒 = 1微秒 / 1000 MICROSECONDS : 1微秒 = 1毫秒 / 1000 MILLISECONDS : 1毫秒 = 1秒 /1000 SECONDS : 秒 MINUTES : 分 HOURS : 小時 DAYS : 天

  • workQueue:執行緒池中的任務佇列:維護著等待執行的Runnable物件

當所有的核心執行緒都在幹活時,新新增的任務會被新增到這個佇列中等待處理,如果佇列滿了,則新建非核心執行緒執行任務。

  • threadFactory:建立執行緒的方式。

  • handler:異常處理程式。

既然已經知道了任務執行,那麼任務是怎麼排隊的呢?

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * 1. 如果執行的執行緒少於corepoolSize大小,新任務會直接開啟新的執行緒執行。
         * 對addWorker的呼叫原子性地檢查執行狀態和workerCount,從而通過返回false防止假警報,假警報會在不應該的情況下新增執行緒。
         *
         * 2. 如果一個任務成功的加入佇列,我們需要再次檢查是否需要開啟新的執行緒來執行。
         * 可能原因有:已有任務執行完畢,或者執行緒池已經被結束。
         * 
         *
         * 3. 如果不能對任務進行排隊,則嘗試新增一個新任務執行緒。
         * 如果它失敗了,我們知道我們已經關閉或飽和了所以拒絕這個任務。
         */
         
        //執行狀態標籤
        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);
    }

複製程式碼

看到這裡我們已經模模糊糊的明白了任務排隊執行,所有的任務佇列都是一樣的排隊執行,那麼我們任務佇列又有哪些呢?

  • LinkedBlockingQueue:線性阻塞佇列。接收到任務,如果沒超過corePoolSize,則建立新執行緒執行,否則進入阻塞佇列等待

  • ArrayBlockingQueue:陣列阻塞佇列。陣列特診是長度固定,也就是這個佇列長度固定。接收到新任務,如果沒超過corePoolSize,則建立新執行緒執行,如果超過,則建立新執行緒(執行緒總數<maximumPoolSize)執行。如果新任務既不能在佇列中等待,又不能執行,丟擲異常。

  • SynchronousQueue:同步佇列。 既然是同步佇列,說明新任務來了就執行。也就是核心執行緒數量無限大。

  • DelayQueue:延遲佇列,聽名字也知道任務要延遲執行,這個佇列接收到任務時,首先先入隊,只有達到了指定的延時時間,才會執行任務。

也就是說到了這裡,我們基本已經分析了執行緒池的幾個核心:jdk自帶執行緒池種類、執行緒池內的執行緒工廠(用於生產執行緒)、執行緒池任務執行、執行緒池任務排隊、執行緒池佇列型別。我們總結一張圖,可以結束本篇文章,當然其他型別的執行緒池具體實現,請自行檢視原始碼。

jdk執行緒池結構模型圖

思考:在Java開發中還有哪些類似的東西是這種操作的呢?

相關文章