Android異步技術(shù)-基礎(chǔ)篇

線程

在操作系統(tǒng)中,線程一般是操作系統(tǒng)能夠進行運算調(diào)度的最小單位,它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務(wù),在Java中線程能力由Thread類實現(xiàn),其使用過程也十分簡單只需三步即可。

1、將需要單獨執(zhí)行的代碼放在一個類的run方法中,這個類需要實現(xiàn)Runnable接口:

class WorkTask implements Runnable {
    @Override
    public void run() {
        // doSomething...
    }
}
WorkTask workTask = new WorkTask();

如果使用的是Java8及以上版本,也可以直接使用lambda表達式創(chuàng)建:

Runnable workTask = () -> {
    // doSomething
};

2、使用創(chuàng)建的Runnadble對象構(gòu)造Thread對象

Thread workThread = new Thread(workTask);

3、啟動線程

workThread.start();

另外,由于Thread類本身實現(xiàn)了Runnable接口,所以我們也可以直接集成Thread類然后復(fù)寫它的run方法來定義線程,需要注意的是,不管通過哪種方式創(chuàng)建線程,如果要啟動它,都必須調(diào)用start()方法,不能直接調(diào)用run方法,直接調(diào)用run方法會在原線程執(zhí)行其中的代碼,而不是啟動新線程。

線程的狀態(tài)

線程從創(chuàng)建出來之后會有以下幾種狀態(tài)

  • New(新建)

  • Runnable(可運行)

  • Blocked(阻塞)

  • Waiting(等待)

  • Timed waiting(計時等待)

  • Terminated(終止)

新建線程

使用new創(chuàng)建完一個Thread對象之后,該線程就處于新建狀態(tài),此時這個線程還沒有開始運行,其中的代碼也沒有被執(zhí)行。

可運行線程

當調(diào)用了start方法之后,線程就處于可運行狀態(tài),可運行意味著這個線程可能正在運行,也可能沒有運行,需要操作系統(tǒng)提供具體的運行時間,Java語言規(guī)范并沒有將運行中作為一個單獨的狀態(tài),一個正在運行的狀態(tài)仍然處于可運行狀態(tài)。

阻塞和等待線程

當線程處于阻塞或者等待狀態(tài)時,它不運行任何代碼,而且消耗最少的資源,需要由線程調(diào)度器重新激活這個線程。

  • 當一個線程試圖獲取一個內(nèi)部的對象鎖,而這個鎖目前被其他線程占有,該線程就會被阻塞,進入阻塞狀態(tài),當所有其他線程都釋放了這個鎖,并且線程調(diào)度器允許該線程持有這個鎖時,它將變成非阻塞狀態(tài)。

  • 當線程等待另一個線程通知調(diào)度器出現(xiàn)一個條件時,這個線程會進入等待狀態(tài),例如調(diào)用了Object.wait()Thread.join()、Lock.tryLock()、Condition.await()等方法

  • 如果在調(diào)用上述等待狀態(tài)的方法時傳入了超時參數(shù),線程就回進入計時等待狀態(tài),額外的還有調(diào)用Thread.sleep()方法也會進入計時等待狀態(tài)。

終止線程

有兩個原因會導致線程終止:

  • run方法執(zhí)行完畢,正常退出,線程自然終止
  • 發(fā)生了未被捕獲的異常,導致線程意外終止

PS:由于Thread.stop()方法已被廢棄,所以線程無法被手動終止,如需手動終止一個線程,可使用interrupt方法,然后在線程的run方法中不停的檢查當前線程是否被中斷,以決定是否繼續(xù)執(zhí)行run方法中的代碼。

狀態(tài)切換

下圖展示了線程可能的狀態(tài)以及各個狀態(tài)間的切換,當一個線程阻塞或者處于等待狀態(tài)時,線程調(diào)度器可以調(diào)度另一個線程運行,當線程被重新激活,調(diào)度器會檢查他是否比當前運行的線程有更高的優(yōu)先級,如果是,則會剝奪某個線程的運行權(quán),選擇高優(yōu)先級的線程運行。

image.png

線程池

在日常開發(fā)中,由于線程的創(chuàng)建和銷毀比較耗費系統(tǒng)資源,所以應(yīng)該盡可能的復(fù)用線程,線程池就提供了線程復(fù)用的能力,在線程池中包含很多準備運行的線程,當我們提交一個runnable對象到線程池中時,就會有線程來調(diào)用它的run方法,當run方法退出時,這個線程不會死亡,而是留在池中等待準備為下一個請求服務(wù)。

可以通過new ThreadPoolExecutor()來構(gòu)造一個自己的線程池,其構(gòu)造方法如下:

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

各個參數(shù)的含義分別是:

  • corePoolSize:核心線程數(shù)
  • maximumPoolSize:線程池中最大線程數(shù)量
  • keepAliveTime:當線程池中空閑線程數(shù)量超過corePoolSize時,多余的線程會在多長時間內(nèi)被銷毀
  • unit:keepAliveTime的時間單位
  • workQueue:任務(wù)隊列,被添加到線程池中,但尚未被執(zhí)行的任務(wù)
  • threadFactory:線程工廠,用于創(chuàng)建線程
  • handler:拒絕策略,當任務(wù)太多來不及處理時,如何拒絕任務(wù)

在上面的參數(shù)中,corePoolSizemaximumPoolSize決定了線程池中的線程數(shù)量,其中corePoolSize表示正常情況下的線程數(shù)量,maximumPoolSize表示當任務(wù)隊列已滿時最多創(chuàng)建的線程數(shù)量,keepAliveTimeunit決定了超過核心線程數(shù)的剩余線程的存活時間,threadFactory為線程工廠,一般使用默認的即可,如果需要對線程名等做自定義則需自己提供,workQueuehandler較為復(fù)雜,下面單獨介紹。

workQueue任務(wù)隊列

線程池采用生產(chǎn)者-消費者模型處理處理任務(wù),當生產(chǎn)者通過Executor.execute(Runnable)方法提交一個任務(wù)到線程池時,線程池會根據(jù)當前的任務(wù)執(zhí)行情況和線程的空閑情況決定這個任務(wù)是立即執(zhí)行還是放入一個阻塞隊列中,常用的阻塞隊列有直接提交隊列、有界任務(wù)隊列、無界任務(wù)隊列、優(yōu)先任務(wù)隊列幾種,下面分別說明。

直接提交隊列

使用SynchronousQueue實現(xiàn),包路徑為java.util.concurrent.SynchronousQueue,是一種特殊的阻塞隊列,沒有容量,提交的任務(wù)不會被保存,總是會立即執(zhí)行,如果當前線程池內(nèi)的線程數(shù)量小于maximumPoolSize,則會立即嘗試創(chuàng)建新的線程執(zhí)行任務(wù),否則會直接執(zhí)行拒絕策略。

image.png

上圖中核心線程數(shù)為1,最大線程數(shù)為2,拒絕策略為直接拋出異常,通過日志可以發(fā)現(xiàn),在創(chuàng)建并運行了2個線程后,在嘗試提交第3個任務(wù)時,執(zhí)行了拒絕策略,拋出了異常。

有界的任務(wù)隊列

使用ArrayBlockingQueue實現(xiàn),其底層采用數(shù)組實現(xiàn),使用有界任務(wù)隊列當有新的任務(wù)提交時,如果當前線程池中的線程數(shù)量小于corePoolSize,則直接創(chuàng)建新的線程執(zhí)行任務(wù),否則將任務(wù)緩存到隊列中,當隊列已滿時,則繼續(xù)創(chuàng)建線程直到池中的線程數(shù)量達到maximumPoolSize,此時如果還有新的任務(wù)提交,則執(zhí)行拒絕策略。

[圖片上傳失敗...(image-9aeeeb-1614848387267)]

在上圖中,最大線程數(shù)為2,采用有界任務(wù)隊列,可以看到,在執(zhí)行了2個任務(wù)后,直接執(zhí)行了拒絕策略,然后再執(zhí)行了5個任務(wù),總共提交了10個任務(wù),可是最終只執(zhí)行了7個,這是因為開始執(zhí)行了2個任務(wù),然后有界任務(wù)隊列中緩存了5個任務(wù),在提交最后3個任務(wù)時,執(zhí)行了拒絕策略,任務(wù)丟失,未被執(zhí)行。

無界任務(wù)隊列

使用LinkedBlockingQueue實現(xiàn),底層采用鏈表實現(xiàn),可以無限的往隊列中添加任務(wù),線程池中線程的最大的數(shù)量就是corePoolSize,此時的maximumPoolSize參數(shù)是無效的,使用無界任務(wù)隊列需注意任務(wù)的提交與處理之間的平衡,如果大量的任務(wù)堆積無法處理,比較容易造成資源耗盡的情況。

image.png

在上圖中,核心線程數(shù)為2,最大線程數(shù)為4,采用無界任務(wù)隊列,共提交了10個任務(wù),通過日志可以看出,只創(chuàng)建了2個線程去執(zhí)行這些任務(wù),并未創(chuàng)建更多的線程去執(zhí)行任務(wù)。

優(yōu)先級任務(wù)隊列

使用PriorityBlockingQueue實現(xiàn),是一個特殊的無界隊列,與無界隊列的區(qū)別在于無界隊列是按照先進先出的順序執(zhí)行任務(wù),而優(yōu)先級隊列是根據(jù)的任務(wù)的優(yōu)先級順序來執(zhí)行任務(wù),任務(wù)的優(yōu)先級通過Comparable接口判斷,如果向采用優(yōu)先級隊列的線程池提交了未實現(xiàn)Comparable接口的任務(wù),則會報ClassCastException。

image.png
image.png
拒絕策略

在上面的示例中,已經(jīng)使用了ThreadPoolExecutor自帶的AbortPolicy,當任務(wù)無法處理時,執(zhí)行該策略直接拋出了異常,ThreadPoolExecutor還提供了另外幾個拒絕策略可供選擇:

  • AbortPolicy:直接拋出異常,阻止系統(tǒng)正常工作
  • CallerRunsPolicy:把任務(wù)隊列中的任務(wù)放在調(diào)用者線程當中運行
  • DiscardOledestPolicy:丟棄任務(wù)隊列中最老的一個任務(wù),也就是當前任務(wù)隊列中最先被添加進去的,馬上要被執(zhí)行的那個任務(wù),并嘗試再次提交
  • DiscardPolicy策略:丟棄無法處理的任務(wù),不予任何處理。當然使用此策略,業(yè)務(wù)場景中需允許任務(wù)的丟失

根據(jù)不同的業(yè)務(wù)場景可以選擇以上幾種策略中的任何一個,另外如果以上策略都無法滿足需求,也可通過實現(xiàn)RejectedExecutionHandler接口,自定義拒絕策略

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

只需實現(xiàn)上面的接口,并在線程池初始化時傳入即可,在需要執(zhí)行拒絕策略時`rejectedExecution就會被調(diào)用,參數(shù)分別為當前提交的任務(wù)和當前的線程池對象。

默認線程池配置

Java通過Executors類提供了很多默認的線程池配置,其內(nèi)部也是通過對上述參數(shù)的不同配置實現(xiàn)的,如果有滿足需求的場景可以直接使用

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

采用立即提交隊列,核心線程數(shù)為0,最大線程數(shù)為Integer.MAX_VALUE,線程最長存活60s,適用于有大量短生命周期任務(wù)提交的場景。

Executors.newFixedThreadPool(int)
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

核心線程數(shù)和最大線程數(shù)均為傳入的int型參數(shù),采用無界隊列,包含固定數(shù)目的線程數(shù)量,且線程會一直保留。

Executors.newSingleThreadExecutor()
return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));

只有一個線程的線程池,所有的任務(wù)會被串行提交執(zhí)行。

參考

《Java核心技術(shù)-卷一》

java線程池ThreadPoolExecutor類使用詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 什么是線程池?為什么要使用線程池? 將線程池化,需要運行任務(wù)時就從里面拿出來一個,不需要了就放回去,不需要每次都n...
    閆回閱讀 448評論 0 1
  • 一、使用線程池的優(yōu)點 提高線程的可管理性:線程是稀缺資源,如果無限制的創(chuàng)建,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定...
    youzhihua閱讀 155評論 0 0
  • 1:線程的概念 進程(任務(wù)):一個正在運行的程序 進程的調(diào)度:CPU來決定什么時候該運行哪個進程 (時間片輪流法)...
    小小一技術(shù)驛站閱讀 159評論 0 0
  • 線程池這個大家必會的把,就算咱是做 android 的,有現(xiàn)成的異步組件但是也保不齊什么時候得自己寫個異步處理,再...
    前行的烏龜閱讀 859評論 0 2
  • 推薦指數(shù): 6.0 書籍主旨關(guān)鍵詞:特權(quán)、焦點、注意力、語言聯(lián)想、情景聯(lián)想 觀點: 1.統(tǒng)計學現(xiàn)在叫數(shù)據(jù)分析,社會...
    Jenaral閱讀 5,950評論 0 5

友情鏈接更多精彩內(nèi)容