線程池閱讀

博客地址

類繼承的結構

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OYI3uqsH-1574346852727)(…/…/…/…/uploads/javasource/util/executor.jpeg)]

Executor是最頂層的接口,定義了execute(Runnable runnable)方法。ExecutorService繼承了Executor,繼承了execute方法,還定義很多接口方法,例如shutdown、isTerminated、submit等方法。

在下面一層是AbstractExecutorService,這是一個抽象類,實現一些很有用的方法供子類使用。

ThreadPoolExecutor是我們線程池的實現。

ThreadPoolExecutor

ThreadPoolExecutor實現了一個線程池需要的各個方法,它實現了任務提交、線程管理、監控等等方法。

我們還可以在它的基礎上進行擴展,比如實現定時任務的類 ScheduledThreadPoolExecutor(用來在給定延時後執行異步任務或者週期性執行任務) 就繼承自 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.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;
}

我們創建一個線程池參數最全的構造方法如上,這些是我們最關心的參數。

  • corePoolSize 核心線程數
  • maximumPoolSize 最大線程數
  • keepAliveTime 空閒線程的存活時間
  • unit 存活時間的單位
  • workQueue 任務隊列,BlockingQueue 接口的某個實現(常使用 ArrayBlockingQueue 和 LinkedBlockingQueue)。
  • threadFactory 用於創建線程,一般都使用默認的(Executors.defaultThreadFactory()),我們可以通過它將我們的線程的名字設置得比較可讀一些,如 Message-Thread-1, Message-Thread-2 類似這樣。
  • handler 拒絕策略

狀態流轉

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

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;

COUNT_BITS 設置爲 29(32-3),前三位用於存放線程狀態,後29位用於存放線程數
CAPACITY = 2^29-1=536870911 即線程池的最大數量
RUNNING 111 00000000000000000000000000000 接受新的任務,處理等待隊列中的任務
SHUTDOWN 000 00000000000000000000000000000 不接受新的任務提交,但是會繼續處理等待隊列中的任務
STOP 001 00000000000000000000000000000 不接受新的任務提交,不再處理等待隊列中的任務,中斷正在執行任務的線程
TIDYING 010 00000000000000000000000000000 所有的任務都銷燬了,線程池的狀態在轉換爲 TIDYING 狀態時,會執行鉤子方法 terminated()
TERMINATED 011 00000000000000000000000000000 terminated() 方法結束後,線程池的狀態就會變成這個

  • RUNNING -> SHUTDOWN:當調用了 shutdown() 後,會發生這個狀態轉換,這也是最重要的
  • (RUNNING or SHUTDOWN) -> STOP:當調用 shutdownNow() 後,會發生這個狀態轉換,這下要清楚 shutDown() 和 shutDownNow() 的區別了
  • SHUTDOWN -> TIDYING:當任務隊列和線程池都清空後,會由 SHUTDOWN 轉換爲 TIDYING
  • STOP -> TIDYING:當任務隊列清空後,發生這個轉換
  • TIDYING -> TERMINATED:這個前面說了,當 terminated() 方法結束後

Worker

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
{
    private static final long serialVersionUID = 6138294804551838833L;

    final Thread thread;
    Runnable firstTask;
    volatile long completedTasks;

    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

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

    protected boolean isHeldExclusively() {
        return getState() != 0;
    }

    protected boolean tryAcquire(int unused) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    protected boolean tryRelease(int unused) {
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

    public void lock()        { acquire(1); }
    public boolean tryLock()  { return tryAcquire(1); }
    public void unlock()      { release(1); }
    public boolean isLocked() { return isHeldExclusively(); }

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

Worker是線程池中的內部類,是線程池中真正執行任務的線程,Worker繼承了AbstractQueuedSynchronizer(aqs)實現了Runable。
Worker中thread是真正的線程;firstTask是在創建線程的時候,如果同時指定了這個線程起來以後需要執行的第一個任務,那麼第一個任務就是存放在這裏的;completedTasks 存放了此線程完成的任務數
構造函數傳入firstTask,也可以傳 null。run方法調用了外部類的runWorker方法。其餘的方法用 AQS 操作,來獲取這個線程的執行權,用了獨佔鎖。

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線程啓動後調用,如果指定了該worker的firstTask,則先執行這個任務,之後通過while循環從隊列中取任務。
首先lock,然後判斷線程池狀態大於等於 STOP,那麼意味着該線程也要中斷。
beforeExecute方法是一個鉤子方法,留給需要的子類進行實現;然後執行任務;afterExecutey也是鉤子方法,將task和異常作爲參數,留給子類實現使用;最後將task置爲空,準備getTask,worker的完成任數加1,釋放獨佔鎖。
如果能執行到最後的finally(對線程池進行關閉)有兩種可能 1 getTask 返回 null,也就是說,隊列中已經沒有任務需要執行了,執行關閉。2 任務執行過程中發生了異常。

getTask
private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        //檢查狀態
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            //減少工作線程的數量,返回null CAS操作
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);
        //核心線程是否超時回收 | 線程數大於核心線程數
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        // 線程數大於最大線程數,因爲有可能開發者調用了 setMaximumPoolSize() 將線程池的 maximumPoolSize 調小了,那麼多餘的 Worker 就需要被關閉
        // 並且 線程數 > 1 獲取隊列爲King,
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            //減少線程數 返回空
            if (compareAndDecrementWorkerCount(c))
                return null;
            //CAS
            continue;
        }
        try {
            // 到 workQueue 中獲取任務
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

此方法有三種可能:

  1. 阻塞直到獲取到任務返回。()默認 corePoolSize 之內的線程是不會被回收的,它們會一直等待任務)
  2. 超時退出。keepAliveTime 起作用的時候,也就是如果這麼多時間內都沒有任務,那麼應該執行關閉
  3. 如果發生了以下條件,此方法必須返回 null:
    • 池中有大於 maximumPoolSize 個 workers 存在(通過調用 setMaximumPoolSize 進行設置)
    • 線程池處於 SHUTDOWN,而且 workQueue 是空的,前面說了,這種不再接受新的任務
    • 線程池處於 STOP,不僅不接受新的線程,連 workQueue 中的線程也不再執行

execute 方法

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    
    int c = ctl.get();
    //當前線程數少於核心線程數,那麼直接添加一個 worker 來執行任務,創建一個新的線程,並把當前任務 command 作爲這個線程的第一個任務(firstTask)
    if (workerCountOf(c) < corePoolSize) {
        //添加任務成功,那麼就結束了,返回 false 代表線程池不允許提交任務
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //如果線程池處於 RUNNING 狀態,把這個任務添加到任務隊列 workQueue 中
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        //如果線程池已不處於 RUNNING 狀態,那麼移除已經入隊的這個任務,並且執行拒絕策略
        if (! isRunning(recheck) && remove(command))
            reject(command);
        //線程池還是 RUNNING 的,並且線程數爲 0,那麼開啓新的線程
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //如果失敗,說明當前線程數已經達到 maximumPoolSize,執行拒絕策略
    else if (!addWorker(command, false))
        reject(command);
}

執行的流程很簡單,如果當前線程數少於核心線程數直接add一個worker執行,如果大於等於核心線程數會加進任務隊列等待worker執行,如果任務隊列滿了之後,繼續添加worker執行,如果此時線程數超過最大線程數就會執行拒絕策略。

拒絕策略

CallerRunsPolicy
public static class CallerRunsPolicy implements RejectedExecutionHandler {
    public CallerRunsPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}

如果線程池沒有被關閉,那麼由提交任務的線程自己來執行這個任務。

public static class AbortPolicy implements RejectedExecutionHandler {
    public AbortPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}

線程池的默認策略,直接拋出異常。

public static class DiscardPolicy implements RejectedExecutionHandler {
    public DiscardPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    }
}

不處理直接忽略。

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    public DiscardOldestPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

把隊列隊頭的任務幹掉,然後提交這個任務。

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