深入理解在Android中線程池的使用

本文轉自博客Vander丶CSDN博客

博客地址:http://blog.csdn.net/l540675759/article/details/62230562

前言

(1)本文共花費2周零3天的凌晨時光,這段時間收穫很多.

(2)從整理文章,作者從線程-->阻塞隊列-->二進制-->線程池的內部機制,一路走來,本來是想寫一篇爲AsyncTask做鋪墊的文章,沒想到越寫越多.

(3)文章中如果錯誤,請大家及時指正,作者會及時更新.

(4)希望大家能夠從文章中.多多收穫,迄今爲止,博主最好的一篇文章,也是花了大力氣最用心的一篇文章.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

線程

在瞭解線程池之前,先給大家介紹下線程的概念

這裏寫圖片描述

先看一個燒水的例子,圖中看電視是主線,用戶想在看電視的過程中去完成燒水這個操作,並且不耽誤看電視,看了這張圖,在去了解接下來的概念會更好的理解主線程與子線程的概念。

線程是什麼?

從底層角度來說: 
一個線程就是在進程中的一個單一的順序控制流.

而單個進程可以擁有多個併發執行的任務,每個任務都好像有自己的CPU一樣,而其底層的機制就是切分CPU的時間,也就是CPU將輪流給每個任務分配其佔用時間。

每個任務都覺得自己在一直佔用CPU,而事實上是將CPU時間劃分成片段分配給所有的任務。

在多個CPU的環境下,多線程的運作,可以極大的提供程序的運行速度,這就是線程存在的意義。


那麼在Android中,線程的作用是?

首先,先了解下Android下進程和線程的概念: 
這裏引用Gityuan作者在知乎上的回答,關於線程和進程的概念

進程:每個app運行時前首先創建一個進程,該進程是由Zygote fork出來的,用於承載App上運行的各種Activity/Service等組件。 
進程對於上層應用來說是完全透明的,這也是google有意爲之,讓App程序都是運行在Android Runtime。大多數情況一個App就運行在一個進程中,除非在AndroidManifest.xml中配置Android:process屬性,或通過native代碼fork進程。

線程:線程對應用來說非常常見,比如每次new Thread().start都會創建一個新的線程。該線程與App所在進程之間資源共享,從Linux角度來說進程與線程除了是否共享資源外,並沒有本質的區別,都是一個task_struct結構體,在CPU看來進程或線程無非就是一段可執行的代碼,CPU採用CFS調度算法,保證每個task都儘可能公平的享有CPU時間片。


上面可能還是比較專業,這裏簡要總結下線程在Android的作用:

(1)在Android中線程分主線程和子線程,主線程也被稱爲UI線程,用來處理各種和界面相關的事情, 
例 :界面的加載,Activity的生命週期這些都在主線程的範疇之內。

(2)由於主線程比較特殊,因爲本身主線程在處理界面上,用了大部分的消耗,所以主線程不能再處理過於耗時的操作(IO操作,網絡請求,大量的數據操作),否則就會造成ANR現象(程序卡死)。

什麼是ANR?,這裏百度上有比較全的介紹

而造成這種現象的主要原因有:

Activity響應時間超過5s

Broadcast在處理時間超過10s

Service處理時間超過20s

這大部分的原因是主線程進行過於耗時的操作,因爲Activity,Broadcast,Serivce本身都是通過主線程進行承載的。

(3)此時子線程就橫空出世解決了這類問題,Android建議耗時操作必須放在子線程中運行。

(4)而在Android中可以解決耗時問題的角色除了Thread之外還有AsyncTask,HandlerThread,IntentService,都可以實現此類功能,而他們的本質還是傳統的線程。

這裏寫圖片描述


爲什麼會有線程池?

從字面上來看,線程池是存放,和管理線程的池子。那麼爲什麼會有線程池呢?

先看一個例子,這裏我用Handler和Thread來模擬網絡請求的操作:

    private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == TASK_ACTION) {
                Log.d("收到消息", "更新UI");
            }
            return false;
        }
    });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //模擬網絡請求
                    Thread.sleep(1000);
                    mHandler.sendEmptyMessage(TASK_ACTION);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

上面過程,只是用一個Thread來模擬正常的網絡請求,然後通過Handler來回調給UI線程,通知UI線程來刷新,如果對Handler機制不太瞭解,

一篇不錯的Handler介紹的文章

上面只是單純的一個網絡請求,那麼現在需求來了,這個界面不止一個網絡請求,可能存在大量的網絡請求,這時候就會有問題產生:

(1)當大量的網絡請求產生,就會大量的創建和銷燬線程,因此可能會造成過大的性能開銷。

(2)當大量的線程一起運作的時候,可能會造成資源緊張,上面也介紹過線程底層的機制就是切分CPU的時間,而大量的線程同時存在時可能造成互相搶佔資源的現象發生,從而導致阻塞的現象。

基於以上背景,線程池適當的出現可以很好的解決上述的問題,而上述模擬網絡請求也只是一個簡單的例子,而現實情況下,會有好多種情況和上述相似,比如在數據庫操作大數據,多線程下載,在使用Thread的同時都會出現上述情況。


什麼是線程池?

Android中的線程池的概念來源於Java中的Executor,Executor是一個接口,真正的線程池的實現爲ThreadPoolExecutor,ThreadPoolExecutor提供了一系列參數來配置線程池,通過不同的參數可以創建不同的線程池。

線程池的優點:

線程池的出現,恰恰就是解決上面類似問題的痛點,而線程池的優點有:

(1)複用線程池中的線程,避免因爲線程的創建和銷燬所帶來的性能開銷。

(2)能夠有效的控制線程池的最大併發數,避免大量的線程之間因互相搶佔系統資源而導致的阻塞現象。

(3)能夠對線程進行簡單的管理,並提供定時執行以及指定間隔循環執行等功能。


線程池的構造方法

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                    BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

上面代碼是創建一個基本的線程池需要的參數,讓我們通過圖來簡要的描述下:

ThreadPoolExecute的構造方法參數

由上圖可以簡要的描述出創建一個基本的線程池需要的參數,以及各個參數的含義,下面將詳細說明各個參數的具體含義。


CorePoolSize 
線程的核心線程數。

默認情況下,核心線程數會在線程中一直存活,即使它們處於閒置狀態。

如果將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置爲true,那麼核心線程就會存在超時策略,這個時間間隔有keepAliveTime所決定,當等待時間超過keepAliveTime所指定的時長後,核心線程就會被停止。


maximumPoolSize 
線程池所能容納的最大線程數。

當活動線程數達到這個數值後,後續的新任務將會被阻塞。


keepAliveTime 
非核心線程閒置時的超時時長,超過這個時長,非核心線程就會被回收,當ThreadPoolExector的allowCoreThreadTimeOut屬性設置爲True時,keepAliveTime同樣會作用於核心線程。


unit 
用於指定keepAliveTime參數的時間單位,這是一個枚舉,常用的有TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分鐘)等。

TimeUnit.NANOSECONDS  納秒
TimeUnit.MICROSECONDS 微秒
TimeUnit.MILLISECONDS 毫秒
TimeUnit.SECONDS    秒
TimeUnit.MINUTES    分鐘
TimeUnit.HOURS      小時
TimeUnit.DAYS
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

workQueue 
線程池中的任務隊列,通過線程池execute方法提交的Runnable對象會存儲在這個參數中。

這個任務隊列是BlockQueue類型,屬於阻塞隊列,就是當隊列爲空的時候,此時取出任務的操作會被阻塞,等待任務加入隊列中不爲空的時候,才能進行取出操作,而在滿隊列的時候,添加操作同樣被阻塞。

如果有想了解的可以參考下這篇文章: 
Java多線程-工具篇-BlockingQueue


threadFactory 
線程工廠,爲線程池提供創建新線程的功能。ThreadFactory是一個接口,它只有一個方法,newThread(Runnable r),用來創建線程。

        ThreadFactory factory =new ThreadFactory() {
        //線程安全的Integer操作類
            private final AtomicInteger mCount =new AtomicInteger(1);

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "new Thread #" + mCount.getAndIncrement());
            }
        };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

線程池的源碼解析

打開源碼,先把線程池源碼中除了構造參數,其他的一些基本屬性,先給分析一下.

線程池的生命週期

    //這裏在線程池統計數值,用AtomicInteger,它是一種線程安全的加減操作類
    //初始生命週期是RUNNING,工作線程的初始數量是0 
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

    //進行移位操作需要的常量 Integer.SIZE =32 bit位
    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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

將上述高位運算就是將 0 和1以及其他的數值在二進制下,向左移位29位,缺位用0補齊,實際結果就變成:

# 接受新任務,並且處理隊列任務的狀態
RUNNING     = 111 000...000 (290)
# 不接受新任務,但是會處理隊列任務的狀態  
SHUTDOWN    = 000 000...000 (290不包括前三位)
# 不接受新任務,並且也不會處理隊列任務的狀態
STOP        = 001 000...000 (290)
# 所有線程池內線程都將被終止,並且將workCount清零,在這裏狀態下將會運行terminated()方法(終止線程池的方法)
TIDYING     = 010 000...000 (290)
# terminated()方法以及結束的狀態
TERMINATED  = 011 000...000 (290)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
     /**
      * 獲取到當前線程池的生命週期的狀態
      */
    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; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

上面的三個函數是獲取當前線程池狀態的方法,這裏簡單介紹下: 
(1) ctlOf()有兩個參數,一個是生命週期狀態,一個是當前線程池工作線程. 
生命週期的狀態格式: 
XXX 0000…0000(29個0) 
ctlOf()返回的值就是將工作線程數量轉化成2進制拼接在生命週期的二進制後半段上.

(2) runStateOf()和workerCountOf()方法都是讓生命週期的狀態值與CAPACITY和CAPACITY的反碼進行與運算,簡明的說,就是獲得二進制數的高位(前三位)和低位(後29位).

如果大家比較瞭解位運算可以發現:

CAPACITY        ------>     000 1111...1111 (29個1)
~CAPACITY      ------>     111 0000...0000 (29個0)
  • 1
  • 2

所以在進行與運算的同時,可以分別取出前3位和後29位,來分別代表線程池的生命週期和工作線程數.


其他屬性

    /**
     * 無法執行任務的通知類
     * 在Android中不太常用
     */
    private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

當線程池無法執行任務,這可能由於任務隊列已滿或者是無法成功執行任務.這個時候ThreadPoolExecution就會調用handler的rejectedExecution方法來通知調用者.

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                " rejected from " +
                e.toString());
    }
  • 1
  • 2
  • 3
  • 4
  • 5

默認情況下,rejectedExecution會拋出個RejectedExecutionException異常,來說明爲什麼當前無法執行任務.

ThreadPoolExecution爲RejectedExecutionException提供了幾個可選值:

----------------------------CallerRunsPolicy-------------------
//拒絕任務時,判斷線程池的狀態是否爲SHUTDOWN,如果是任務將會被丟棄,如果不是的話任務會被繼續執行.
public void rejectedExecution(Runnable r, ThreadPoolExecutor e){
            if (!e.isShutdown()) {
                r.run();
            }
        }

-------------------------AbortPolicy(默認值)---------------------
//拒絕任務時,直接拋出異常和原因
public void rejectedExecution(Runnable r, ThreadPoolExecutor e){
            throw new RejectedExecutionException(
            "Task " + r.toString() +
            " rejected from " +e.toString());
        }

-------------------------DiscardPolicy--------------------------
//就是單純的拒絕任務而已,什麼也不會發生,任務也將丟失public void rejectedExecution(Runnable r, ThreadPoolExecutor e){
        //什麼沒發生
        }

----------------------DiscardOldestPolicy-----------------------
//拒絕任務時,判斷線程池的狀態是否爲SHUTDOWN,如果是任務將會被丟棄,如果不是的話,將當前請求隊列中等待時間最長的任務彈出,將其加入隊列中.
public void rejectedExecution(Runnable r, ThreadPoolExecutor e){
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

比較重要的方法

線程池有兩個執行的方法,分別是submit()和execute(),這兩個方法本質的含義是一樣的.

submit()和execute()

從圖上可以看出的,submit()其實還是需要調用execute()去執行任務,而submit()和execute()本質上的不同是submit()將包裝好的任務進行了返回.

submit()

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        //還是通過調用execute
        execute(ftask);
        //最後會將包裝好的Runable返回
        return ftask;
    }

//將Callable<T> 包裝進FutureTask中
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

//可以看出FutureTask也是實現Runnable接口,因爲RunableFuture本身就繼承了Runnabel接口
public class FutureTask<V> implements RunnableFuture<V> {
    .......
}

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

execute()

    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();
        }
        //判斷線程池是否爲RUNNING狀態,並且將任務添加至隊列中.
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //審覈下線程池的狀態,如果不是RUNNING狀態,直接移除隊列中
            if (! isRunning(recheck) && remove(command))
                reject(command);
                //如果當前線程數量爲0,則單獨創建線程,而不指定任務.
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //如果不滿足上述條件,嘗試創建一個非核心線程來執行任務,如果創建失敗,調用reject()方法.
        else if (!addWorker(command, false))
            reject(command);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

下圖是一張execute()方法的基本流程: 
execute()方法的流程

從execute()方法中,能看出addWorker()方法,是創建線程(核心線程,非核心線程)的主要方法,而reject()就是線程創建失敗的一個回調.


reject()

那我們來看一下reject()方法,這裏就是通過上述的Handler將通知發出去.然後針對不同的類型的RejectedExecutionHandler,進行不同的處理,這裏我們上文有介紹.

    final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
    }
  • 1
  • 2
  • 3
  • 4

下面我們着重看下創建線程的方法: 
addWorker()

參數 :

Runnable firstTask:

爲傳遞進來需要執行的任務,也可以設置爲null(在SHUTDOWN情況下,單純的創建線程來執行任務).

boolean core:

需要創建的線程是否需要是核心線程.

 private boolean addWorker(Runnable firstTask, boolean core) {
        //類似goto,是Java的標識符,在這裏出現是爲了防止在多線程的情況下,compareAndIncrementWorkerCount(),計算線程池狀態出現問題,而設立重試的關鍵字.
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            //看似判斷條件很麻煩
            //分拆後主要兩點
            //線程已經處於STOP或者即將STOP的狀態
            //或者 處於SHUTDOWN狀態,並且傳遞的任務爲null,此時隊列不爲空還需要增加線程,除了這種情況,其他情況都不需要增加線程
            //以上的情況就不需要
            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;
                //如果不成功則跳到外層循環入口,重新執行.
                retry inner loop
            }
        }

        //下面是創建線程的過程,並且在創建線程的過程中加鎖
        //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 {
                    int rs = runStateOf(ctl.get());

                    //從這裏可以看出線程池創建線程,只會在兩種情況下創建:
                    //1.線程池在RUNNING狀態(rs<SHUTDOWN)
                    //2.線程池處於SHUTDOWN狀態,並且任務爲null,但是此時任務隊列不爲空,需要繼續增加線程來加快處理進度.
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        //在這裏就是先檢查下Thread狀態,防止意外發生.
                        if (t.isAlive()) 
                            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);
        }
        //如果添加成功就返回true,否則添加失敗就返回false.
        return workerStarted;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

addWorker()方法的注意事項:

(1)增加一個線程,並且會爲其綁定core或者maximum的線程標誌.

(2)如果成功添加線程來執行當前任務,那麼當前線程池的狀態會被刷新.

(3)在添加第一個任務firstTask的這種情況下,新的工作線程會被創建後立即執行任務.

(4)該方法會在線程池STOP狀態或者符合資格去關閉會返回false.

(5)線程工廠創建線程失敗的時候,同樣也會返回false.

(6)在由於線程創建失敗,線程工廠返回的線程爲null,或者發生異常(通常由於在線程執行的過程中發生了OOM),線程池會進行回滾操作.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

addWorker()的處理流程


addWorker()方法執行的幾個階段

第一階段 :

狀態檢查

在創建線程時,首先檢查線程池狀態,防止線程處於STOP,TIDYING,TERMINATED狀態,如果處於上述狀態直接返回false.

然後對於在SHUTDOWN狀態下,只有當前任務隊列不爲空,並且傳遞的任務參數爲null.這種狀態下可以創建線程來執行剩餘任務,除此之外全部直接返回false.
  • 1
  • 2
  • 3
   if (rs >= SHUTDOWN &&! (rs == SHUTDOWN && firstTask == null 
            &&! workQueue.isEmpty()))
                return false;
  • 1
  • 2
  • 3

第二階段 :

判斷當前線程池的能否創建線程以及可以創建之後的數量添加校驗

(1)當前線程的數量是否超過線程池的最大容量,以及根據core參數來判斷是否超過設置的核心線程數,和最大線程數.

(2)通過第一步之後就可以創建線程,這裏需要用到compareAndIncrementWorkerCount()通過原子操作來更新線程池的線程數量變化,如果變化數量失敗,這裏有一個重試機制,這個retry關鍵字就是來完成這個操作.

(3)這裏註明下CAPACITY這個常量就是線程池的線程數量的極限
CAPACITY  = 1>>29 -1 =2^29-1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
            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;
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

第三階段 :

創建線程

通過上述階段,那麼就可以創建線程了,這裏設置了兩個初始的標誌位,來判斷被創建線程的狀態.
        boolean workerStarted = false;
        boolean workerAdded = false;
如果最終線程創建並添加成功,則返回true,如果線程最終沒有被運行,則調用addWorkerFailed()方法.

由於邏輯並不複雜,這裏就不貼代碼了.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

其他相關方法

addWorkedFailed()

在addWorker()方法中,如果線程創建之後,沒有最終運行(workerStarted=false)這時候會調用addWorkedFailed()方法.

    /**
     * 回滾工作線程的創建操作:
     * 1.如果線程的包裝類Worker存在,就將其remove掉.
     * 2.remove掉添加線程失敗的Worker,需要刷新當前工作線程的數量
     * 3.嘗試終止操作,並且終止這個線程的操作.
     */
    private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (w != null)
                workers.remove(w);
            decrementWorkerCount();
            //嘗試停止操作.
            tryTerminate();
        } finally {
            mainLock.unlock();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

tryTerminate()

而在addWorkedFailed()方法中,我們發現除了回滾操作,它還調用了tryTerminate()方法,嘗試着去停止線程池.因爲線程池創建線程失敗一般由於異常引起(或OOM),所以這時候需要讓線程池進行停止操作.

注意事項:

如果發生以下兩種情況,使用該方法將會將線程池轉換爲終止狀態(TERMINATED):

1.SHUTDOWN狀態下,隊列爲空的情況下.

2.STOP狀態下.

如果符合上述條件,可以轉換終止狀態時,這時會中斷當前線程池內空閒的線程,以確保終止的信號的傳遞.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
    final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            //檢測當前是RUNNING狀態,或者已經停止(TERMINATED)的狀態,或者SHUTDOWN狀態下,隊列不爲空.
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;

                //如果工作線程的數量不爲空,這時候需要處理空閒線程,這裏只中斷一個其中一個線程,這裏博主認爲是將線程池的狀態由SHUTDOWN向STOP狀態過渡的信號.
            if (workerCountOf(c) != 0) { 
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
            //設置當前的線程池狀態爲TIDYING,如果設置失敗,還會進入循環直到設置成功.
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                    //停止方法的空實現
                        terminated();
                    } finally {
                        //最終線程池會設置爲停止狀態
                        ctl.set(ctlOf(TERMINATED, 0));
                 //設置可重新入鎖的標誌,將被鎖隔離的在外等待的所有線程喚醒.  
                      termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }

        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

interruptIdleWorkers()

而在tryTerminate()方法中,這裏中斷線程的操作就是由interruptIdleWorkers()方法進行的.

這個方法作用很明確,就是設置線程中斷操作的方法,唯一注意的地方就是參數onlyOne:

如果爲true,只中斷工作線程中的一個線程.
如果爲false,中斷所有的工作線程.
  • 1
  • 2
 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();
                    }
                }
                //如果onlyOne參數爲True,則只執行一次就跳出.
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

shutdown() 
而中斷所有空閒的線程方法則是shutdown()方法,它的核心方法還是調用interruptIdleWorkers()方法.

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            //校驗線程的狀態
            checkShutdownAccess();
            //設置線程池狀態爲SHUTDOWN
            advanceRunState(SHUTDOWN);
            //中斷所有空閒進程.調用的interruptIdleWorkers(false);
            interruptIdleWorkers();
            //需要自己實現,在中斷所有線程可定製的操作
            onShutdown(); 
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

注意事項: 
(1)在shutdown()執行時可以讓現有的任務被執行,但是新的任務不在會被處理.

(2)如果已經是SHUTDOWN狀態,那麼繼續調用不會產生任何效果.

(3)這個方法不會等待所有任務都執行完在提交,如果想先讓所有任務先完成請使用awaitTermination()方法.


awaitTermination()

阻塞至直到當前任務完全結束之後,纔會關閉請求,或者超時的發生,也或者線程被中斷 看哪個發生的快.

參數:d 
timeout —- 設置超時時間 
unit —- 設置超時時間的單位

 public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        //設置時間
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
        //這是死循環,當線程池的狀態爲TERMINATED時,跳出循環返回true,也就是所有任務都完成.否則超時或者線程中斷則返回false.
            while (!runStateAtLeast(ctl.get(), TERMINATED)) {
                if (nanos <= 0L)
                    return false;
                nanos = termination.awaitNanos(nanos);
            }
            return true;
        } finally {
            mainLock.unlock();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

線程池的分類

Android中最常見的四類具有不同功能特性的線程池:

1.FixedThreadPool

//特點:
//核心線程數和最大線程數相同.
//無超時時間
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(
                nThreads, nThreads,
                0L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>()
        );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

這是一種數量固定的線程池,當線程處於空閒的時候,並不會被回收,除非線程池被關閉.

當所有的線程都處於活動狀態時,新任務都會處於等待狀態,直到有線程空閒出來.

由於FixedThreadPool中只有核心線程並且這些核心線程不會被回收,這意味着它能夠更加快速地響應外界的請求.

通過構造方法可以看出,FixedThreadPool只有核心線程,並且超時時間爲0(即無超時時間),所以不會被回收.

FixedThreadPool線程池圖解


2.CacheThreadPool

//無核心線程,並且最大線程數爲int的最大值.
//超時時間爲60s
//隊列爲SynchronousQueue同步阻塞隊列,隊列中沒有任何容量.只有在有需求的情況下,隊列中纔可以試着添加任務.

    public static  ExecutorService newCacheThreadPool(){
        return  new ThreadPoolExecutor(
                0,Integer.MAX_VALUE,
                60L,TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>()
        );
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

它是一種線程數量不定的線程池,它只有非核心線程,並且其最大線程數爲Integer.MAX_VALUE(也就相當於線程池的線程數量可以無限大).

當線程池中所有線程都處於活動的狀態時,線程池會創建新的線程來處理新任務,否則就會複用空閒線程來處理.

值得注意的是,這個線程池中儲存任務的隊列是SynchronousQueue隊列,這個隊列可以理解爲無法儲存的隊列,只有在可以取出的情況下,纔會向其內添加任務.

從整個CacheThreadPool的特性來看:

(1)比較適合執行大量的耗時較少的任務.

(2)當整個線程都處於閒置狀態時,線程池中的線程都會超時而被停止,這時候的CacheThreadPool幾乎不佔任何系統資源的.

CacheThreadPool


3.ScheduledThreadPool

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSzie) {
        return new ScheduledThreadPoolExecutor(corePoolSzie);
    }

//核心線程數是固定的,非核心線程無限大,並且非核心線程數有10s的空閒存活時間

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
                DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
                new DelayedWorkQueue());
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

它的核心線程數量是固定的,而非核心線程數是沒有限制的,並且當非核心線程閒置時會被立即回收.

ScheduThreadPool這類線程池主要用於執行定時任務和具有固定週期的重複任務.

而DelayedWorkQueue這個隊列就是包裝過的DelayedQueue,這個類的特點是在存入時會有一個Delay對象一起存入,代表需要過多少時間才能取出,相當於一個延時隊列.

ScheduleThreadPool

4.SingleThreadExecutor

    public static ExecutorService newSingleThreadExecutor() {
        return Executors.newSingleThreadExecutor();
    }
    //特點:
    //線程中只有一個核心線程
    //並且無超時時間
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                        0L, TimeUnit.MILLISECONDS,
                        new LinkedBlockingQueue<Runnable>()));
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

這類線程池內部只有一個核心線程,它確保所有的任務都在同一個線程中按順序執行.

SingleThreadExecutor的意義在於統一外界所有任務到一個線程,這使得這些任務之間不需要處理線程同步的問題.

SingleThreadPool圖解


參考文檔:

1.安卓開發藝術探索

2.ThreadPoolExecutor解析-主要源碼研究
http://blog.csdn.net/wenhuayuzhihui/article/details/51377174

3.理解java線程的中斷(interrupt)
http://blog.csdn.net/canot/article/details/51087772
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章