【android】深刻理解在Android:線程池的使用原理分析

本專欄專一分享大型Bat面試知識,後續會持續更新,喜歡的話麻煩點擊一個關注

1.簡介

線程池能夠簡單看作是一組線程的集合,經過使用線程池,咱們能夠方便的複用線程,避免了頻繁建立和銷燬線程所帶來的開銷。在應用上,線程池可應用在後端相關服務中。好比 Web 服務器,數據庫服務器等。以 Web 服務器爲例,假如 Web 服務器會收到大量短時的 HTTP 請求,若是此時咱們簡單的爲每一個 HTTP 請求建立一個處理線程,那麼服務器的資源將會很快被耗盡。固然咱們也能夠本身去管理並複用已建立的線程,以限制資源的消耗量,但這樣會使用程序的邏輯變複雜。好在,幸運的是,咱們沒必要那樣作。在 JDK 1.5 中,官方已經提供了強大的線程池工具類。經過使用這些工具類,咱們能夠用低廉的代價使用多線程技術。java

線程池做爲 Java 併發重要的工具類,在會用的基礎上,我以爲頗有必要去學習一下線程池的相關原理。畢竟線程池除了要管理線程,還要管理任務,同時還要具有統計功能。因此多瞭解一點,仍是能夠擴充眼界的,同時也能夠更爲熟悉線程池技術。面試

2.繼承體系

線程池所涉及到的接口和類並非不少,其繼承體系也相對簡單。相關繼承關係以下:數據庫

如上圖,最頂層的接口 Executor 僅聲明瞭一個方法execute。ExecutorService 接口在其父類接口基礎上,聲明瞭包含但不限於shutdownsubmitinvokeAllinvokeAny 等方法。至於 ScheduledExecutorService 接口,則是聲明瞭一些和定時任務相關的方法,好比 schedulescheduleAtFixedRate。線程池的核心實現是在 ThreadPoolExecutor 類中,咱們使用 Executors 調用newFixedThreadPoolnewSingleThreadExecutornewCachedThreadPool等方法建立線程池均是 ThreadPoolExecutor 類型。後端

以上是對線程池繼承體系的簡單介紹,這裏先讓你們對線程池大體輪廓有必定的瞭解。接下來我會介紹一下線程池的實現原理,繼續往下看吧。數組

3.原理分析

3.1 核心參數分析

3.1.1 核心參數簡介

如上節所說,線程池的核心實現即 ThreadPoolExecutor 類。該類包含了幾個核心屬性,這些屬性在可在構造方法進行初始化。在介紹核心屬性前,咱們先來看看 ThreadPoolExecutor 的構造方法,以下:緩存

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

如上所示,構造方法的參數即核心參數,這裏我用一個表格來簡要說明一下各個參數的意義。以下:服務器

參數 說明
corePoolSize 核心線程數。當線程數小於該值時,線程池會優先建立新線程來執行新任務
maximumPoolSize 線程池所能維護的最大線程數
keepAliveTime 空閒線程的存活時間
workQueue 任務隊列,用於緩存未執行的任務
threadFactory 線程工廠。可經過工廠爲新建的線程設置更有意義的名字
handler 拒絕策略。當線程池和任務隊列均處於飽和狀態時,使用拒絕策略處理新任務。默認是 AbortPolicy,即直接拋出異常

以上是各個參數的簡介,下面我將會針對部分參數進行詳細說明,繼續往下看。多線程

3.1.2 線程建立規則

在 Java 線程池實現中,線程池所能建立的線程數量受限於 corePoolSize 和 maximumPoolSize 兩個參數值。線程的建立時機則和 corePoolSize 以及 workQueue 兩個參數有關。下面列舉一下線程建立的4個規則(線程池中無空閒線程),以下:併發

  1. 線程數量小於 corePoolSize,直接建立新線程處理新的任務
  2. 線程數量大於等於 corePoolSize,workQueue 未滿,則緩存新任務
  3. 線程數量大於等於 corePoolSize,但小於 maximumPoolSize,且 workQueue 已滿。則建立新線程處理新任務
  4. 線程數量大於等於 maximumPoolSize,且 workQueue 已滿,則使用拒絕策略處理新任務

簡化一下上面的規則:工具

序號 條件 動做
1 線程數 < corePoolSize 建立新線程
2 線程數 ≥ corePoolSize,且 workQueue 未滿 緩存新任務
3 corePoolSize ≤ 線程數 < maximumPoolSize,且 workQueue 已滿 建立新線程
4 線程數 ≥ maximumPoolSize,且 workQueue 已滿 使用拒絕策略處理

3.1.3 資源回收

考慮到系統資源是有限的,對於線程池超出 corePoolSize 數量的空閒線程應進行回收操做。進行此操做存在一個問題,即回收時機。目前的實現方式是當線程空閒時間超過 keepAliveTime 後,進行回收。除了核心線程數以外的線程能夠進行回收,核心線程內的空閒線程也能夠進行回收。回收的前提是allowCoreThreadTimeOut屬性被設置爲 true,經過public void allowCoreThreadTimeOut(boolean) 方法能夠設置屬性值。

3.1.4 排隊策略

如3.1.2 線程建立規則一節中規則2所說,當線程數量大於等於 corePoolSize,workQueue 未滿時,則緩存新任務。這裏要考慮使用什麼類型的容器緩存新任務,經過 JDK 文檔介紹,咱們可知道有3中類型的容器可供使用,分別是同步隊列有界隊列無界隊列。對於有優先級的任務,這裏還能夠增長優先級隊列。以上所介紹的4中類型的隊列,對應的實現類以下:

實現類 類型 說明
SynchronousQueue 同步隊列 該隊列不存儲元素,每一個插入操做必須等待另外一個線程調用移除操做,不然插入操做會一直阻塞
ArrayBlockingQueue 有界隊列 基於數組的阻塞隊列,按照 FIFO 原則對元素進行排序
LinkedBlockingQueue 無界隊列 基於鏈表的阻塞隊列,按照 FIFO 原則對元素進行排序
PriorityBlockingQueue 優先級隊列 具備優先級的阻塞隊列

3.1.5 拒絕策略

如3.1.2 線程建立規則一節中規則4所說,線程數量大於等於 maximumPoolSize,且 workQueue 已滿,則使用拒絕策略處理新任務。Java 線程池提供了4中拒絕策略實現類,以下:

實現類 說明
AbortPolicy 丟棄新任務,並拋出 RejectedExecutionException
DiscardPolicy 不作任何操做,直接丟棄新任務
DiscardOldestPolicy 丟棄隊列隊首的元素,並執行新任務
CallerRunsPolicy 由調用線程執行新任務

以上4個拒絕策略中,AbortPolicy 是線程池實現類所使用的策略。咱們也能夠經過方法public void setRejectedExecutionHandler(RejectedExecutionHandler)修改線程池決絕策略。

3.2 重要操做

3.2.1 線程的建立與複用

在線程池的實現上,線程的建立是經過線程工廠接口ThreadFactory的實現類來完成的。默認狀況下,線程池使用Executors.defaultThreadFactory()方法返回的線程工廠實現類。固然,咱們也能夠經過

public void setThreadFactory(ThreadFactory)方法進行動態修改。具體細節這裏就很少說了,並不複雜,你們能夠本身去看下源碼。

在線程池中,線程的複用是線程池的關鍵所在。這就要求線程在執行完一個任務後,不能當即退出。對應到具體實現上,工做線程在執行完一個任務後,會再次到任務隊列獲取新的任務。若是任務隊列中沒有任務,且 keepAliveTime 也未被設置,工做線程則會被一致阻塞下去。經過這種方式便可實現線程複用。

說完原理,再來看看線程的建立和複用的相關代碼(基於 JDK 1.8),以下:

+----ThreadPoolExecutor.Worker.java
Worker(Runnable firstTask) {
    setState(-1);
    this.firstTask = firstTask;
    // 調用線程工廠建立線程
    this.thread = getThreadFactory().newThread(this);
}

// Worker 實現了 Runnable 接口
public void run() {
    runWorker(this);
}

+----ThreadPoolExecutor.java
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock();
    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);
    }
}

3.2.2 提交任務

一般狀況下,咱們能夠經過線程池的submit方法提交任務。被提交的任務可能會當即執行,也可能會被緩存或者被拒絕。任務的處理流程以下圖所示:

上面的流程圖不是很複雜,下面再來看看流程圖對應的代碼,以下:

+---- AbstractExecutorService.java
public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    // 建立任務
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    // 提交任務
    execute(ftask);
    return ftask;
}

+---- ThreadPoolExecutor.java
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();
    }
    
    // 緩存任務,若是隊列已滿,則 offer 方法返回 false。不然,offer 返回 true
    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);
    }
    
    // 添加工做者對象,並在 addWorker 方法中檢測線程數是否小於最大線程數
    else if (!addWorker(command, false))
        // 線程數 >= 最大線程數,使用拒絕策略處理任務
        reject(command);
}

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 {
                int rs = runStateOf(ctl.get());
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    // 將 worker 對象添加到 workers 集合中
                    workers.add(w);
                    int s = workers.size();
                    // 更新 largestPoolSize 屬性
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                // 開始執行任務
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

上面的代碼略多,不過結合上面的流程圖,和我所寫的註釋,理解主邏輯應該不難。

3.2.3 關閉線程池

咱們能夠經過shutdownshutdownNow兩個方法關閉線程池。兩個方法的區別在於,shutdown 會將線程池的狀態設置爲SHUTDOWN,同時該方法還會中斷空閒線程。shutdownNow 則會將線程池狀態設置爲STOP,並嘗試中斷全部的線程。中斷線程使用的是Thread.interrupt方法,未響應中斷方法的任務是沒法被中斷的。最後,shutdownNow 方法會將未執行的任務所有返回。

調用 shutdown 和 shutdownNow 方法關閉線程池後,就不能再向線程池提交新任務了。對於處於關閉狀態的線程池,會使用拒絕策略處理新提交的任務。

4.幾種線程池

通常狀況下,咱們並不直接使用 ThreadPoolExecutor 類建立線程池,而是經過 Executors 工具類去構建線程池。經過 Executors 工具類,咱們能夠構造5中不一樣的線程池。下面經過一個表格簡單介紹一下幾種線程池,以下:

靜態構造方法 說明
newFixedThreadPool(int nThreads) 構建包含固定線程數的線程池,默認狀況下,空閒線程不會被回收
newCachedThreadPool() 構建線程數不定的線程池,線程數量隨任務量變更,空閒線程存活時間超過60秒後會被回收
newSingleThreadExecutor() 構建線程數爲1的線程池,等價於 newFixedThreadPool(1) 所構造出的線程池
newScheduledThreadPool(int corePoolSize) 構建核心線程數爲 corePoolSize,可執行定時任務的線程池
newSingleThreadScheduledExecutor() 等價於 newScheduledThreadPool(1)

關於我

更多信息能夠點擊關於我 , 很是但願和你們一塊兒交流 , 共同進步
領取以下資料:
https://shimo.im/docs/Jp8PTkp... 《Android面試.進階.視頻大全目錄》
圖片描述

圖片描述

相關文章
相關標籤/搜索