[轉(zhuǎn)]深入理解在Android中線程池的使用

這是一篇 寫的非常用心的 博文,這里是原文地址
https://blog.csdn.net/l540675759/article/details/62230562

前言

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

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

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

(4)希望大家能夠從文章中.多多收獲,迄今為止,博主最好的一篇文章,也是花了大力氣最用心的一篇文章.

線程

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

image.png

先看一個燒水的例子胖翰,圖中看電視是主線,用戶想在看電視的過程中去完成燒水這個操作氨淌,并且不耽誤看電視既绕,看了這張圖尿扯,在去了解接下來的概念會更好的理解主線程與子線程的概念闺阱。

線程是什么殉摔?

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

而單個進(jìn)程可以擁有多個并發(fā)執(zhí)行的任務(wù)州胳,每個任務(wù)都好像有自己的CPU一樣灶似,而其底層的機制就是切分CPU的時間捆毫,也就是CPU將輪流給每個任務(wù)分配其占用時間。

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

在多個CPU的環(huán)境下瓤湘,多線程的運作瓢颅,可以極大的提供程序的運行速度,這就是線程存在的意義弛说。


那么在Android中挽懦,線程的作用是?

首先木人,先了解下Android下進(jìn)程和線程的概念:
這里引用Gityuan作者在知乎上的回答信柿,關(guān)于線程和進(jìn)程的概念

進(jìn)程:每個app運行時前首先創(chuàng)建一個進(jìn)程,該進(jìn)程是由Zygote fork出來的醒第,用于承載App上運行的各種Activity/Service等組件渔嚷。
進(jìn)程對于上層應(yīng)用來說是完全透明的,這也是google有意為之淘讥,讓App程序都是運行在Android Runtime圃伶。大多數(shù)情況一個App就運行在一個進(jìn)程中,除非在AndroidManifest.xml中配置Android:process屬性蒲列,或通過native代碼fork進(jìn)程窒朋。

線程:線程對應(yīng)用來說非常常見,比如每次new Thread().start都會創(chuàng)建一個新的線程蝗岖。該線程與App所在進(jìn)程之間資源共享侥猩,從Linux角度來說進(jìn)程與線程除了是否共享資源外,并沒有本質(zhì)的區(qū)別抵赢,都是一個task_struct結(jié)構(gòu)體欺劳,在CPU看來進(jìn)程或線程無非就是一段可執(zhí)行的代碼,CPU采用CFS調(diào)度算法铅鲤,保證每個task都盡可能公平的享有CPU時間片划提。


上面可能還是比較專業(yè),這里簡要總結(jié)下線程在Android的作用:

(1)在Android中線程分主線程和子線程邢享,主線程也被稱為UI線程鹏往,用來處理各種和界面相關(guān)的事情,
例 :界面的加載骇塘,Activity的生命周期這些都在主線程的范疇之內(nèi)伊履。

(2)由于主線程比較特殊,因為本身主線程在處理界面上款违,用了大部分的消耗唐瀑,所以主線程不能再處理過于耗時的操作(IO操作,網(wǎng)絡(luò)請求插爹,大量的數(shù)據(jù)操作)哄辣,否則就會造成ANR現(xiàn)象(程序卡死)。

什么是ANR?柔滔,這里百度上有比較全的介紹

而造成這種現(xiàn)象的主要原因有:

Activity響應(yīng)時間超過5s

Broadcast在處理時間超過10s

Service處理時間超過20s

這大部分的原因是主線程進(jìn)行過于耗時的操作溢陪,因為Activity,Broadcast睛廊,Serivce本身都是通過主線程進(jìn)行承載的。

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

(4)而在Android中可以解決耗時問題的角色除了Thread之外還有AsyncTask,HandlerThread邓馒,IntentService嘶朱,都可以實現(xiàn)此類功能,而他們的本質(zhì)還是傳統(tǒng)的線程光酣。

image.png

為什么會有線程池疏遏?

從字面上來看,線程池是存放救军,和管理線程的池子财异。那么為什么會有線程池呢?

先看一個例子唱遭,這里我用Handler和Thread來模擬網(wǎng)絡(luò)請求的操作:

    private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == TASK_ACTION) {
                Log.d("收到消息", "更新UI");
            }
            return false;
        }
    });
  new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //模擬網(wǎng)絡(luò)請求
                    Thread.sleep(1000);
                    mHandler.sendEmptyMessage(TASK_ACTION);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

上面過程戳寸,只是用一個Thread來模擬正常的網(wǎng)絡(luò)請求,然后通過Handler來回調(diào)給UI線程拷泽,通知UI線程來刷新疫鹊,如果對Handler機制不太了解,

一篇不錯的Handler介紹的文章

上面只是單純的一個網(wǎng)絡(luò)請求司致,那么現(xiàn)在需求來了拆吆,這個界面不止一個網(wǎng)絡(luò)請求,可能存在大量的網(wǎng)絡(luò)請求脂矫,這時候就會有問題產(chǎn)生:

(1)當(dāng)大量的網(wǎng)絡(luò)請求產(chǎn)生枣耀,就會大量的創(chuàng)建和銷毀線程,因此可能會造成過大的性能開銷羹唠。

(2)當(dāng)大量的線程一起運作的時候奕枢,可能會造成資源緊張,上面也介紹過線程底層的機制就是切分CPU的時間佩微,而大量的線程同時存在時可能造成互相搶占資源的現(xiàn)象發(fā)生缝彬,從而導(dǎo)致阻塞的現(xiàn)象。

基于以上背景哺眯,線程池適當(dāng)?shù)某霈F(xiàn)可以很好的解決上述的問題谷浅,而上述模擬網(wǎng)絡(luò)請求也只是一個簡單的例子,而現(xiàn)實情況下,會有好多種情況和上述相似一疯,比如在數(shù)據(jù)庫操作大數(shù)據(jù)撼玄,多線程下載,在使用Thread的同時都會出現(xiàn)上述情況墩邀。


什么是線程池掌猛?

Android中的線程池的概念來源于Java中的Executor,Executor是一個接口眉睹,真正的線程池的實現(xiàn)為ThreadPoolExecutor荔茬,ThreadPoolExecutor提供了一系列參數(shù)來配置線程池,通過不同的參數(shù)可以創(chuàng)建不同的線程池竹海。

線程池的優(yōu)點:

線程池的出現(xiàn)慕蔚,恰恰就是解決上面類似問題的痛點,而線程池的優(yōu)點有:

(1)復(fù)用線程池中的線程斋配,避免因為線程的創(chuàng)建和銷毀所帶來的性能開銷孔飒。

(2)能夠有效的控制線程池的最大并發(fā)數(shù),避免大量的線程之間因互相搶占系統(tǒng)資源而導(dǎo)致的阻塞現(xiàn)象艰争。

(3)能夠?qū)€程進(jìn)行簡單的管理坏瞄,并提供定時執(zhí)行以及指定間隔循環(huán)執(zhí)行等功能。


線程池的構(gòu)造方法

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

上面代碼是創(chuàng)建一個基本的線程池需要的參數(shù)园细,讓我們通過圖來簡要的描述下:

image.png

由上圖可以簡要的描述出創(chuàng)建一個基本的線程池需要的參數(shù)惦积,以及各個參數(shù)的含義,下面將詳細(xì)說明各個參數(shù)的具體含義猛频。


CorePoolSize
線程的核心線程數(shù)狮崩。

默認(rèn)情況下,核心線程數(shù)會在線程中一直存活鹿寻,即使它們處于閑置狀態(tài)睦柴。

如果將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設(shè)置為true,那么核心線程就會存在超時策略毡熏,這個時間間隔有keepAliveTime所決定坦敌,當(dāng)?shù)却龝r間超過keepAliveTime所指定的時長后,核心線程就會被停止痢法。


maximumPoolSize
線程池所能容納的最大線程數(shù)狱窘。

當(dāng)活動線程數(shù)達(dá)到這個數(shù)值后,后續(xù)的新任務(wù)將會被阻塞财搁。


keepAliveTime
非核心線程閑置時的超時時長蘸炸,超過這個時長,非核心線程就會被回收尖奔,當(dāng)ThreadPoolExector的allowCoreThreadTimeOut屬性設(shè)置為True時搭儒,keepAliveTime同樣會作用于核心線程穷当。


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

TimeUnit.NANOSECONDS  納秒
TimeUnit.MICROSECONDS 微秒
TimeUnit.MILLISECONDS 毫秒
TimeUnit.SECONDS    秒
TimeUnit.MINUTES    分鐘
TimeUnit.HOURS      小時
TimeUnit.DAYS       天

workQueue
線程池中的任務(wù)隊列铃岔,通過線程池execute方法提交的Runnable對象會存儲在這個參數(shù)中汪疮。

這個任務(wù)隊列是BlockQueue類型,屬于阻塞隊列德撬,就是當(dāng)隊列為空的時候铲咨,此時取出任務(wù)的操作會被阻塞,等待任務(wù)加入隊列中不為空的時候蜓洪,才能進(jìn)行取出操作,而在滿隊列的時候坯苹,添加操作同樣被阻塞隆檀。

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


threadFactory
線程工廠,為線程池提供創(chuàng)建新線程的功能粹湃。ThreadFactory是一個接口恐仑,它只有一個方法,newThread(Runnable r)为鳄,用來創(chuàng)建線程裳仆。

        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());
            }
        };

線程池的源碼解析

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

線程池的生命周期

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

    //進(jìn)行移位操作需要的常量 Integer.SIZE =32 bit位
    private static final int COUNT_BITS = Integer.SIZE - 3;
    //進(jìn)行位運算需要的常量
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    //進(jìn)行高位運算
    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;

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

# 接受新任務(wù),并且處理隊列任務(wù)的狀態(tài)
RUNNING     = 111 000...000 (29個0)
# 不接受新任務(wù),但是會處理隊列任務(wù)的狀態(tài)  
SHUTDOWN    = 000 000...000 (29個0不包括前三位)
# 不接受新任務(wù),并且也不會處理隊列任務(wù)的狀態(tài)
STOP        = 001 000...000 (29個0)
# 所有線程池內(nèi)線程都將被終止,并且將workCount清零,在這里狀態(tài)下將會運行terminated()方法(終止線程池的方法)
TIDYING     = 010 000...000 (29個0)
# terminated()方法以及結(jié)束的狀態(tài)
TERMINATED  = 011 000...000 (29個0)
     /**
      * 獲取到當(dāng)前線程池的生命周期的狀態(tài)
      */
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
     /**
      * 獲取當(dāng)前線程池的工作線程狀態(tài)
      */
    private static int workerCountOf(int c)  { return c & CAPACITY; }
     /**
      * 通過或運算拼接線程的生命周期狀態(tài)和工作線程的個數(shù)
      */
    private static int ctlOf(int rs, int wc) { return rs | wc; }

上面的三個函數(shù)是獲取當(dāng)前線程池狀態(tài)的方法,這里簡單介紹下:
(1) ctlOf()有兩個參數(shù),一個是生命周期狀態(tài),一個是當(dāng)前線程池工作線程.
生命周期的狀態(tài)格式:
XXX 0000…0000(29個0)
ctlOf()返回的值就是將工作線程數(shù)量轉(zhuǎn)化成2進(jìn)制拼接在生命周期的二進(jìn)制后半段上.

(2) runStateOf()和workerCountOf()方法都是讓生命周期的狀態(tài)值與CAPACITY和CAPACITY的反碼進(jìn)行與運算,簡明的說,就是獲得二進(jìn)制數(shù)的高位(前三位)和低位(后29位).

如果大家比較了解位運算可以發(fā)現(xiàn):

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

所以在進(jìn)行與運算的同時,可以分別取出前3位和后29位,來分別代表線程池的生命周期和工作線程數(shù).


其他屬性

    /**
     * 無法執(zhí)行任務(wù)的通知類
     * 在Android中不太常用
     */
    private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();

當(dāng)線程池?zé)o法執(zhí)行任務(wù),這可能由于任務(wù)隊列已滿或者是無法成功執(zhí)行任務(wù).這個時候ThreadPoolExecution就會調(diào)用handler的rejectedExecution方法來通知調(diào)用者.

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

默認(rèn)情況下,rejectedExecution會拋出個RejectedExecutionException異常,來說明為什么當(dāng)前無法執(zhí)行任務(wù).

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

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

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

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

----------------------DiscardOldestPolicy-----------------------
//拒絕任務(wù)時,判斷線程池的狀態(tài)是否為SHUTDOWN,如果是任務(wù)將會被丟棄,如果不是的話,將當(dāng)前請求隊列中等待時間最長的任務(wù)彈出,將其加入隊列中.
public void rejectedExecution(Runnable r, ThreadPoolExecutor e){
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }

比較重要的方法

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

image.png

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

submit()

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

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

//可以看出FutureTask也是實現(xiàn)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();
}

execute()

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        //獲得當(dāng)前線程的生命周期對應(yīng)的二進(jìn)制狀態(tài)碼
        int c = ctl.get();
        //判斷當(dāng)前線程數(shù)量是否小于核心線程數(shù)量,如果小于就直接創(chuàng)建核心線程執(zhí)行任務(wù),創(chuàng)建成功直接跳出,失敗則接著往下走.
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //判斷線程池是否為RUNNING狀態(tài),并且將任務(wù)添加至隊列中.
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //審核下線程池的狀態(tài),如果不是RUNNING狀態(tài),直接移除隊列中
            if (! isRunning(recheck) && remove(command))
                reject(command);
                //如果當(dāng)前線程數(shù)量為0,則單獨創(chuàng)建線程,而不指定任務(wù).
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //如果不滿足上述條件,嘗試創(chuàng)建一個非核心線程來執(zhí)行任務(wù),如果創(chuàng)建失敗,調(diào)用reject()方法.
        else if (!addWorker(command, false))
            reject(command);
    }

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


image.png

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


reject()

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

    final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
    }


下面我們著重看下創(chuàng)建線程的方法:
addWorker()

參數(shù) :

Runnable firstTask:

為傳遞進(jìn)來需要執(zhí)行的任務(wù),也可以設(shè)置為null(在SHUTDOWN情況下,單純的創(chuàng)建線程來執(zhí)行任務(wù)).

boolean core:

需要創(chuàng)建的線程是否需要是核心線程.

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

            //看似判斷條件很麻煩
            //分拆后主要兩點
            //線程已經(jīng)處于STOP或者即將STOP的狀態(tài)
            //或者 處于SHUTDOWN狀態(tài),并且傳遞的任務(wù)為null,此時隊列不為空還需要增加線程,除了這種情況,其他情況都不需要增加線程
            //以上的情況就不需要
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            //判斷當(dāng)前工作線程數(shù)量是否超過最大值
            //或者當(dāng)前工作線程數(shù)量超過 核心線程數(shù)或者最大線程數(shù),這個值根據(jù)第二個布爾變量決定
            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;

            //這段函數(shù)是判斷 線程池狀態(tài)的統(tǒng)計更新成沒成功
            //如果成功直接跳出這個循環(huán),繼續(xù)執(zhí)行
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                //如果不成功則跳到外層循環(huán)入口,重新執(zhí)行.
                retry inner loop
            }
        }

        //下面是創(chuàng)建線程的過程,并且在創(chuàng)建線程的過程中加鎖
        //Worker就是線程的一個包裝類.
        //這里分別對線程的創(chuàng)建成功和失敗分別做出了處理.
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
            //創(chuàng)建線程的過程中,加鎖防止并發(fā)現(xiàn)象發(fā)生.
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    int rs = runStateOf(ctl.get());

                    //從這里可以看出線程池創(chuàng)建線程,只會在兩種情況下創(chuàng)建:
                    //1.線程池在RUNNING狀態(tài)(rs<SHUTDOWN)
                    //2.線程池處于SHUTDOWN狀態(tài),并且任務(wù)為null,但是此時任務(wù)隊列不為空,需要繼續(xù)增加線程來加快處理進(jìn)度.
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        //在這里就是先檢查下Thread狀態(tài),防止意外發(fā)生.
                        if (t.isAlive()) 
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        //這里做了一個容量的判斷 
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                //如果線程已經(jīng)增加成功,然后設(shè)置標(biāo)志
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            //最后如果線程沒有開始,就分發(fā)到添加線程失敗,通過標(biāo)志位來判斷線程是否被添加成功.
            if (! workerStarted)
                addWorkerFailed(w);
        }
        //如果添加成功就返回true,否則添加失敗就返回false.
        return workerStarted;
    }

addWorker()方法的注意事項:

(1)增加一個線程,并且會為其綁定core或者maximum的線程標(biāo)志.

(2)如果成功添加線程來執(zhí)行當(dāng)前任務(wù),那么當(dāng)前線程池的狀態(tài)會被刷新.

(3)在添加第一個任務(wù)firstTask的這種情況下,新的工作線程會被創(chuàng)建后立即執(zhí)行任務(wù).

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

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

(6)在由于線程創(chuàng)建失敗,線程工廠返回的線程為null,或者發(fā)生異常(通常由于在線程執(zhí)行的過程中發(fā)生了OOM),線程池會進(jìn)行回滾操作.
image.png

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

第一階段 :

狀態(tài)檢查

在創(chuàng)建線程時,首先檢查線程池狀態(tài),防止線程處于STOP,TIDYING,TERMINATED狀態(tài),如果處于上述狀態(tài)直接返回false.

然后對于在SHUTDOWN狀態(tài)下,只有當(dāng)前任務(wù)隊列不為空,并且傳遞的任務(wù)參數(shù)為null.這種狀態(tài)下可以創(chuàng)建線程來執(zhí)行剩余任務(wù),除此之外全部直接返回false.
   if (rs >= SHUTDOWN &&! (rs == SHUTDOWN && firstTask == null 
            &&! workQueue.isEmpty()))
                return false;

第二階段 :

判斷當(dāng)前線程池的能否創(chuàng)建線程以及可以創(chuàng)建之后的數(shù)量添加校驗

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

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

(3)這里注明下CAPACITY這個常量就是線程池的線程數(shù)量的極限
CAPACITY  = 1>>29 -1 =2^29-1
            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;
            }

第三階段 :

創(chuàng)建線程

通過上述階段,那么就可以創(chuàng)建線程了,這里設(shè)置了兩個初始的標(biāo)志位,來判斷被創(chuàng)建線程的狀態(tài).
        boolean workerStarted = false;
        boolean workerAdded = false;
如果最終線程創(chuàng)建并添加成功,則返回true,如果線程最終沒有被運行,則調(diào)用addWorkerFailed()方法.

由于邏輯并不復(fù)雜,這里就不貼代碼了.


其他相關(guān)方法

addWorkedFailed()

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

    /**
     * 回滾工作線程的創(chuàng)建操作:
     * 1.如果線程的包裝類Worker存在,就將其remove掉.
     * 2.remove掉添加線程失敗的Worker,需要刷新當(dāng)前工作線程的數(shù)量
     * 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();
        }
    }

tryTerminate()

而在addWorkedFailed()方法中,我們發(fā)現(xiàn)除了回滾操作,它還調(diào)用了tryTerminate()方法,嘗試著去停止線程池.因為線程池創(chuàng)建線程失敗一般由于異常引起(或OOM),所以這時候需要讓線程池進(jìn)行停止操作.

注意事項:

如果發(fā)生以下兩種情況,使用該方法將會將線程池轉(zhuǎn)換為終止?fàn)顟B(tài)(TERMINATED):

1.SHUTDOWN狀態(tài)下,隊列為空的情況下.

2.STOP狀態(tài)下.

如果符合上述條件,可以轉(zhuǎn)換終止?fàn)顟B(tài)時,這時會中斷當(dāng)前線程池內(nèi)空閑的線程,以確保終止的信號的傳遞.

    final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            //檢測當(dāng)前是RUNNING狀態(tài),或者已經(jīng)停止(TERMINATED)的狀態(tài),或者SHUTDOWN狀態(tài)下,隊列不為空.
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;

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

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
            //設(shè)置當(dāng)前的線程池狀態(tài)為TIDYING,如果設(shè)置失敗,還會進(jìn)入循環(huán)直到設(shè)置成功.
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                    //停止方法的空實現(xiàn)
                        terminated();
                    } finally {
                        //最終線程池會設(shè)置為停止?fàn)顟B(tài)
                        ctl.set(ctlOf(TERMINATED, 0));
                 //設(shè)置可重新入鎖的標(biāo)志,將被鎖隔離的在外等待的所有線程喚醒.  
                      termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }

        }
    }

interruptIdleWorkers()

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

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

如果為true,只中斷工作線程中的一個線程.
如果為false,中斷所有的工作線程.
 private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                //檢查線程的狀態(tài)
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                //如果onlyOne參數(shù)為True,則只執(zhí)行一次就跳出.
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

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

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            //校驗線程的狀態(tài)
            checkShutdownAccess();
            //設(shè)置線程池狀態(tài)為SHUTDOWN
            advanceRunState(SHUTDOWN);
            //中斷所有空閑進(jìn)程.調(diào)用的interruptIdleWorkers(false);
            interruptIdleWorkers();
            //需要自己實現(xiàn),在中斷所有線程可定制的操作
            onShutdown(); 
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

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

(2)如果已經(jīng)是SHUTDOWN狀態(tài),那么繼續(xù)調(diào)用不會產(chǎn)生任何效果.

(3)shutdown()方法只會中斷空閑的線程,但是不會影響到已經(jīng)存入隊列的任務(wù),如果需要停止線程池的運行,可以使用awaitTermination()方法.


awaitTermination()

阻塞方法,強行等待當(dāng)前隊列中的任務(wù)全部為TERMINATED狀態(tài),可以設(shè)置超時時間.

參數(shù):d
timeout —- 設(shè)置超時時間
unit —- 設(shè)置超時時間的單位

 public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        //設(shè)置時間
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
        //這是死循環(huán),當(dāng)線程池的狀態(tài)為TERMINATED時,跳出循環(huán)返回true,也就是所有任務(wù)都完成.否則超時或者線程中斷則返回false.
            while (!runStateAtLeast(ctl.get(), TERMINATED)) {
                if (nanos <= 0L)
                    return false;
                nanos = termination.awaitNanos(nanos);
            }
            return true;
        } finally {
            mainLock.unlock();
        }
    }

線程池的分類

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

1.FixedThreadPool

//特點:
//核心線程數(shù)和最大線程數(shù)相同.
//無超時時間
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(
                nThreads, nThreads,
                0L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>()
        );

這是一種數(shù)量固定的線程池,當(dāng)線程處于空閑的時候,并不會被回收,除非線程池被關(guān)閉.

當(dāng)所有的線程都處于活動狀態(tài)時,新任務(wù)都會處于等待狀態(tài),直到有線程空閑出來.

由于FixedThreadPool中只有核心線程并且這些核心線程不會被回收,這意味著它能夠更加快速地響應(yīng)外界的請求.

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

image.png

2.CacheThreadPool

//無核心線程,并且最大線程數(shù)為int的最大值.
//超時時間為60s
//隊列為SynchronousQueue同步阻塞隊列,隊列中沒有任何容量.只有在有需求的情況下,隊列中才可以試著添加任務(wù).

    public static  ExecutorService newCacheThreadPool(){
        return  new ThreadPoolExecutor(
                0,Integer.MAX_VALUE,
                60L,TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>()
        );
    }

它是一種線程數(shù)量不定的線程池,它只有非核心線程,并且其最大線程數(shù)為Integer.MAX_VALUE(也就相當(dāng)于線程池的線程數(shù)量可以無限大).

當(dāng)線程池中所有線程都處于活動的狀態(tài)時,線程池會創(chuàng)建新的線程來處理新任務(wù),否則就會復(fù)用空閑線程來處理.

值得注意的是,這個線程池中儲存任務(wù)的隊列是SynchronousQueue隊列,這個隊列可以理解為無法儲存的隊列,只有在可以取出的情況下,才會向其內(nèi)添加任務(wù).

從整個CacheThreadPool的特性來看:

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

(2)當(dāng)整個線程都處于閑置狀態(tài)時,線程池中的線程都會超時而被停止,這時候的CacheThreadPool幾乎不占任何系統(tǒng)資源的.

image.png

3.ScheduledThreadPool

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

//核心線程數(shù)是固定的,非核心線程無限大,并且非核心線程數(shù)有10s的空閑存活時間

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
                DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
                new DelayedWorkQueue());
    }

它的核心線程數(shù)量是固定的,而非核心線程數(shù)是沒有限制的,并且當(dāng)非核心線程閑置時會被立即回收.

ScheduThreadPool這類線程池主要用于執(zhí)行定時任務(wù)和具有固定周期的重復(fù)任務(wù).

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

image.png

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>()));
    }

這類線程池內(nèi)部只有一個核心線程,它確保所有的任務(wù)都在同一個線程中按順序執(zhí)行.

SingleThreadExecutor的意義在于統(tǒng)一外界所有任務(wù)到一個線程,這使得這些任務(wù)之間不需要處理線程同步的問題.

image.png

參考文檔:

1.安卓開發(fā)藝術(shù)探索

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

3.理解java線程的中斷(interrupt)
http://blog.csdn.net/canot/article/details/51087772

</article>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市孤钦,隨后出現(xiàn)的幾起案子歧斟,更是在濱河造成了極大的恐慌,老刑警劉巖偏形,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件静袖,死亡現(xiàn)場離奇詭異,居然都是意外死亡俊扭,警方通過查閱死者的電腦和手機队橙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來萨惑,“玉大人捐康,你說我怎么就攤上這事∮拱” “怎么了解总?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長朱嘴。 經(jīng)常有香客問我倾鲫,道長粗合,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任乌昔,我火速辦了婚禮隙疚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘磕道。我一直安慰自己供屉,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布溺蕉。 她就那樣靜靜地躺著伶丐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疯特。 梳的紋絲不亂的頭發(fā)上哗魂,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機與錄音漓雅,去河邊找鬼录别。 笑死,一個胖子當(dāng)著我的面吹牛邻吞,可吹牛的內(nèi)容都是我干的组题。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼抱冷,長吁一口氣:“原來是場噩夢啊……” “哼崔列!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起旺遮,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤赵讯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后趣效,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瘦癌,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年跷敬,在試婚紗的時候發(fā)現(xiàn)自己被綠了讯私。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡西傀,死狀恐怖斤寇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拥褂,我是刑警寧澤娘锁,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站饺鹃,受9級特大地震影響莫秆,放射性物質(zhì)發(fā)生泄漏间雀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一镊屎、第九天 我趴在偏房一處隱蔽的房頂上張望惹挟。 院中可真熱鬧,春花似錦缝驳、人聲如沸连锯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽运怖。三九已至,卻和暖如春夏伊,著一層夾襖步出監(jiān)牢的瞬間摇展,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工溺忧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吗购,地道東北人。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓砸狞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親镀梭。 傳聞我的和親對象是個殘疾皇子刀森,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 29,395評論 8 265
  • 【JAVA 線程】 線程 進(jìn)程:是一個正在執(zhí)行中的程序。每一個進(jìn)程執(zhí)行都有一個執(zhí)行順序报账。該順序是一個執(zhí)行路徑研底,或者...
    Rtia閱讀 2,768評論 2 20
  • 一頭烏黑亮麗的秀發(fā),隨意扎成一個馬尾很是可愛透罢。一對彎彎的眉毛彰顯出無限純真榜晦,閃閃發(fā)光的眼睛散發(fā)出靈動透徹的光...
    藍(lán)天白云1134閱讀 388評論 6 7
  • `` console.log() ``
    Baylor_0閱讀 102評論 0 0
  • 她不記得當(dāng)年當(dāng)天具體發(fā)生了什么 只記得路邊橋頭 有一輛破舊的老式自行車 一件白襯衫 和一個燦爛的、為她準(zhǔn)備的微笑
    騎驢電動車閱讀 335評論 0 1