進(jìn)階之路 | 奇妙的Thread之旅

前言

本文已經(jīng)收錄到我的Github個(gè)人博客呜魄,歡迎大佬們光臨寒舍:

我的GIthub博客

需要已經(jīng)具備的知識(shí):

  • Thread的基本概念及使用
  • AsyncTask的基本概念及使用

學(xué)習(xí)導(dǎo)圖:

學(xué)習(xí)導(dǎo)圖

一.為什么要學(xué)習(xí)Thread?

Android中搜贤,幾乎完全沿用了Java中的線程機(jī)制巷查。線程是最小的調(diào)度單位艺糜,在很多情況下為了使APP更加流程地運(yùn)行,我們不可能將很多事情都放在主線程上執(zhí)行,這樣會(huì)造成嚴(yán)重卡頓(ANR),那么這些事情應(yīng)該交給子線程去做矿筝,但對(duì)于一個(gè)系統(tǒng)而言,創(chuàng)建棚贾、銷毀窖维、調(diào)度線程的過程是需要開銷的,所以我們并不能無限量地開啟線程妙痹,那么對(duì)線程的了解就變得尤為重要了铸史。

因此,本篇文章將帶領(lǐng)大家由淺入深细诸,從線程的基礎(chǔ),談到同步機(jī)制陋守,再講到阻塞隊(duì)列震贵,接著提及Android中的線程形態(tài),最終一覽線程池機(jī)制水评。

話不多說猩系,趕緊跟隨筆者開始奇妙的Thread之旅吧!

二.核心知識(shí)點(diǎn)歸納

2.1 線程概述

Q1:含義

線程是CPU調(diào)度的最小單位

注意與進(jìn)程相區(qū)分

Q2:特點(diǎn)

線程是一種受限的系統(tǒng)資源中燥。即線程不可無限制的產(chǎn)生且線程的創(chuàng)建和銷毀都有一定的開銷

  • Q:如何避免頻繁創(chuàng)建和銷毀線程所帶來的系統(tǒng)開銷寇甸?
  • A:采用線程池,池中會(huì)緩存一定數(shù)量的線程疗涉,進(jìn)而達(dá)到效果(PS:下文將為您詳細(xì)講解)

Q3:分類

  • 按用途分為兩類:
  • 主線程:一般一個(gè)進(jìn)程只有一個(gè)主線程拿霉,主要處理界面交互相關(guān)的邏輯

  • 子線程:除主線程之外都是子線程,主要用于執(zhí)行耗時(shí)操作

  • 按形態(tài)可分為三類:
  • AsyncTask:底層封裝了線程池和Handler咱扣,便于執(zhí)行后臺(tái)任務(wù)以及在主線程中進(jìn)行UI操作
  • HandlerThread:一種具有消息循環(huán)的線程绽淘,其內(nèi)部可使用Handler
  • IntentService:一種異步、會(huì)自動(dòng)停止的服務(wù)闹伪,內(nèi)部采用HandlerThreadHandler
關(guān)系圖

想詳細(xì)了解Handler機(jī)制的讀者沪铭,推薦一篇筆者的文章:進(jìn)階之路 | 奇妙的Handler之旅

Q4:如何安全地終止線程壮池?

對(duì)于有多線程開發(fā)經(jīng)驗(yàn)的開發(fā)者,應(yīng)該大多數(shù)在開發(fā)過程中都遇到過這樣的需求杀怠,就是在某種情況下椰憋,希望立即停止一個(gè)線程

比如:做Android開發(fā),當(dāng)打開一個(gè)界面時(shí)赔退,需要開啟線程請(qǐng)求網(wǎng)絡(luò)獲取界面的數(shù)據(jù)橙依,但有時(shí)候由于網(wǎng)絡(luò)特別慢,用戶沒有耐心等待數(shù)據(jù)獲取完成就將界面關(guān)閉离钝,此時(shí)就應(yīng)該立即停止線程任務(wù)票编,不然一般會(huì)內(nèi)存泄露,造成系統(tǒng)資源浪費(fèi)卵渴,如果用戶不斷地打開又關(guān)閉界面慧域,內(nèi)存泄露會(huì)累積,最終導(dǎo)致內(nèi)存溢出浪读,APP閃退

所以昔榴,筆者希望能和大家探究下:如何安全地終止線程?

A1:為啥不使用stop?

Java官方早已將它廢棄碘橘,不推薦使用

  • stop是通過立即拋出ThreadDeath異常互订,來達(dá)到停止線程的目的,此異常拋出有可能發(fā)生在任何一時(shí)間點(diǎn)痘拆,包括在catch仰禽、finally等語句塊中,但是此異常并不會(huì)引起程序退出
  • 異常拋出纺蛆,導(dǎo)致線程會(huì)釋放全部所持有的吐葵,極可能引起線程安全問題

A2:提供單獨(dú)的取消方法來終止線程

示例DEMO

public class MoonRunner implements Runnable {
    private long i;
    //注意的是這里的變量是用volatile修飾
    volatile boolean on = true;

    @Override
    public void run() {
        while (on) {
            i++;
        }
        System.out.println("sTop");
    }
    
//設(shè)置一個(gè)取消的方法
    void cancel() {
        on = false;
    }
}

注意:這里的變量是用volatile修飾踊兜,以保證可見性疾党,關(guān)于volatile的知識(shí),筆者將在下文為您詳細(xì)解析

A3:采用interrupt來終止線程

Thread類定義了如下關(guān)于中斷的方法:

中斷的方法

原理:

  • 調(diào)用Thread對(duì)象的interrupt函數(shù)并不是立即中斷線程陆蟆,只是將線程中斷狀態(tài)標(biāo)志設(shè)置為true

  • 當(dāng)線程運(yùn)行中有調(diào)用其阻塞的函數(shù)時(shí)字支,阻塞函數(shù)調(diào)用之后凤藏,會(huì)不斷地輪詢檢測(cè)中斷狀態(tài)標(biāo)志是否為true,如果為true堕伪,則停止阻塞并拋出InterruptedException異常揖庄,同時(shí)還會(huì)重置中斷狀態(tài)標(biāo)志因此需要在catch代碼塊中需調(diào)用interrupt函數(shù)欠雌,使線程再次處于中斷狀態(tài)

  • 如果中斷狀態(tài)標(biāo)志為false抠艾,則繼續(xù)阻塞,直到阻塞正常結(jié)束

具體的interrupt的使用方式可以參考這篇文章:Java線程中斷的正確姿勢(shì)

2.2 同步機(jī)制

2.2.1 volatile

  • 有時(shí)候僅僅為了讀寫一個(gè)或者兩個(gè)實(shí)例就使用同步synchronized的話桨昙,顯得開銷過大
  • volatile為實(shí)例域的同步訪問提供了免鎖的機(jī)制

Q1:先從Java內(nèi)存模型聊起

  • Java 內(nèi)存模型定義了本地內(nèi)存和主存之間的抽象關(guān)系
  • 線程之間的共享變量存儲(chǔ)在主存
  • 每個(gè)線程都有一個(gè)私有的本地內(nèi)存(工作內(nèi)存)检号,本地內(nèi)存中存儲(chǔ)了該線程共享變量的副本腌歉。
內(nèi)存關(guān)系
  • 線程之間通信的步驟
  • 線程A將其本地內(nèi)存更新過的共享變量刷新到主存中去
  • 線程B主存中去讀取線程A之前已更新過的共享變量

Q2:原子性、可見性和有序性了解多少

a1:原子性Atomicity

  • 定義:原子性操作就是指這些操作是不可中斷的齐苛,要做一定做完翘盖,要么就沒有執(zhí)行
  • 對(duì)基本數(shù)據(jù)類型變量的讀取和賦值操作是原子性操作

注意:這里的賦值操作是指將數(shù)字賦值給某個(gè)變量

下面由DEMO解釋更加通俗易懂

x=3;  //原子性操作
y=x;  //非原子性操作  原因:包括2個(gè)操作:先讀取x的值,再將x的值寫入工作內(nèi)存
x++;  //非原子性操作  原因:包括3個(gè)操作:讀取x的值凹蜂、對(duì)x的值進(jìn)行加1馍驯、向工作內(nèi)存寫入新值
  • volatile不支持原子性(想探究原因的,筆者推薦一篇文章:面試官最愛的volatile關(guān)鍵字
  • 保證整塊代碼原子性(例如i++)的方法:借助于synchronizedLock玛痊,以及并發(fā)包下的atomic的原子操作類

a2:可見性Visibility

  • 定義:一個(gè)線程修改的結(jié)果汰瘫,另一個(gè)線程馬上就能看到

  • Java就是利用volatile來提供可見性的

原因:當(dāng)一個(gè)變量被volatile修飾時(shí),那么對(duì)它的修改會(huì)立刻刷新到主存擂煞,同時(shí)使其它線程的工作內(nèi)存中對(duì)此變量的緩存行失效混弥,因此需要讀取該變量時(shí),會(huì)去內(nèi)存中讀取新值

  • 其實(shí)通過synchronizedLock也能夠保證可見性对省,但是synchronizedLock的開銷都更大

a3:有序性Ordering

  • 指令重排序的定義:大多數(shù)現(xiàn)代微處理器都會(huì)采用將指令亂序執(zhí)行的方法, 在條件允許的情況下, 直接運(yùn)行當(dāng)前有能力立即執(zhí)行的后續(xù)指令, 避開獲取下一條指令所需數(shù)據(jù)時(shí)造成的等待
  • 什么時(shí)候不進(jìn)行指令重排序
  • 符合數(shù)據(jù)依賴性:
//x對(duì)a有依賴
a = 1;
x = a;
  • as-if-serial語義:不管怎么重排序, 單線程程序的執(zhí)行結(jié)果不能被改變
  • 程序順序原則
  1. 如果A happens-before B
  2. 如果B happens-before C
  3. 那么A happens-before C

這就是happens-before傳遞性

  • volatile通過禁止指令重排序的方式來保證有序性

Q3:應(yīng)用場(chǎng)景有哪些蝗拿?

  • 狀態(tài)量標(biāo)記

線程的終止的時(shí)候的狀態(tài)控制,示例DEMO如前文

  • DCL

避免指令重排序:

假定創(chuàng)建一個(gè)對(duì)象需要:

  1. 申請(qǐng)內(nèi)存
  2. 初始化
  3. instance指向分配的那塊內(nèi)存

上面的2和3操作是有可能重排序的, 如果3重排序到2的前面, 這時(shí)候2操作還沒有執(zhí)行, instance!=null, 當(dāng)然不是安全的

class Singleton{
    private volatile static Singleton instance = null;
 
    private Singleton() {}
 
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

Q4:原理:

  • 如果把加入volatile關(guān)鍵字的代碼和未加入volatile關(guān)鍵字的代碼都生成匯編代碼蒿涎,會(huì)發(fā)現(xiàn)加入volatile關(guān)鍵字的代碼會(huì)多出一個(gè)lock前綴指令
  • lock前綴指令實(shí)際相當(dāng)于一個(gè)內(nèi)存屏障哀托,內(nèi)存屏障提供了以下功能:
  • 重排序時(shí)不能把后面的指令重排序到內(nèi)存屏障之前的位置
  • 使得本CPUCache寫入內(nèi)存
  • 寫入動(dòng)作也會(huì)引起別的CPU或者別的內(nèi)核無效化其Cache,相當(dāng)于讓新寫入的值對(duì)別的線程可見

2.2.2 重入鎖與條件對(duì)象

synchronized 關(guān)鍵字自動(dòng)為我們提供了鎖以及相關(guān)的條件劳秋,大多數(shù)需要顯式鎖的時(shí)候仓手,使用synchronized 非常方便,但是當(dāng)我們了解了重入鎖和條件對(duì)象時(shí)玻淑,能更好地理解synchronized 和阻塞隊(duì)列

Q1:重入鎖的定義

  • 可重入鎖指的是可重復(fù)可遞歸調(diào)用的鎖嗽冒,在外層使用鎖之后,在內(nèi)層仍然可以使用岁忘,并且不發(fā)生死鎖辛慰,這樣的鎖就叫做可重入鎖
  • ReentrantLocksynchronized都是可重入鎖

重復(fù)調(diào)用鎖的DEMO如下:

public class ReentrantTest implements Runnable {

    public synchronized void get() {
        System.out.println(Thread.currentThread().getName());
        set();
    }

    public synchronized void set() {
        System.out.println(Thread.currentThread().getName());
    }

    public void run() {
        get();
    }

    public static void main(String[] args) {
        ReentrantTest rt = new ReentrantTest();
        for(;;){
            new Thread(rt).start();
        }
    }
}

Q2:什么是條件對(duì)象Condition区匠?

  • 條件對(duì)象來管理那些已經(jīng)獲得了一個(gè)鎖但是卻不能做有用工作的線程干像,條件對(duì)象又被稱作條件變量
  • 一般要配合ReentrantLock使用,用Condition.await()可以阻塞當(dāng)前線程驰弄,并放棄鎖

Q3:下面說明重入鎖與條件對(duì)象如何協(xié)同使用

  • 支付寶轉(zhuǎn)賬的例子(支付寶打錢麻汰,狗頭.jpg)
  • 場(chǎng)景是這樣的:
//轉(zhuǎn)賬的方法
public void transfer(int from, int to, int amount){
    //alipay是ReentrantLock的實(shí)例
    alipay.lock();
    try{
        //當(dāng)要轉(zhuǎn)給別人的錢大于你所擁有的錢的時(shí)候,調(diào)用Condition的await可以阻塞當(dāng)前線程戚篙,并放棄鎖
        while(accounts[from] < amount){
            condition.await();
        }
                 
        ...//一系列轉(zhuǎn)賬的操作
            //阻塞狀態(tài)解除,進(jìn)入可運(yùn)行狀態(tài)
        condition.signalAll();
    }
    finally{
        alipay.unlock();
    }
}

想要更深一步了解重入鎖的讀者五鲫,可以看下這篇文章:究竟什么是可重入鎖?

2.2.3 synchronized

Q1:synchronized有哪幾種實(shí)現(xiàn)方式岔擂?

  • 同步代碼塊
  • 同步方法

Q2:synchronizedReentrantLock的關(guān)系

  • 兩者都是重入鎖
  • 兩者有些方法互相對(duì)應(yīng)
  • wait等價(jià)于condition.await()
  • notifyAll等價(jià)于condition.signalAll()

Q3:使用場(chǎng)景對(duì)比

類型 使用場(chǎng)景
阻塞隊(duì)列 一般實(shí)現(xiàn)同步的時(shí)候使用
同步方法 如果同步方法適合你的程序
同步代碼塊 不太建議使用位喂,因?yàn)椴僮髌饋砣菀壮鲥e(cuò)
Lock/Condition 需要使用Lock/Condition的獨(dú)有特性時(shí)

2.3 阻塞隊(duì)列

為了更好地理解線程池的知識(shí)浪耘,我們需要了解下阻塞隊(duì)列

Q1:定義

  • 阻塞隊(duì)列BlockingQueue是一個(gè)支持兩個(gè)附加操作的隊(duì)列。這兩個(gè)附加的操作是:
  • 在隊(duì)列為空時(shí)塑崖,獲取元素的線程會(huì)阻塞七冲,直到隊(duì)列變?yōu)榉强?/li>
  • 當(dāng)隊(duì)列滿時(shí),存儲(chǔ)元素的線程會(huì)阻塞规婆,直到隊(duì)列變?yōu)榉菨M

Q2:使用場(chǎng)景

阻塞隊(duì)列常用于生產(chǎn)者和消費(fèi)者的場(chǎng)景澜躺,生產(chǎn)者是往隊(duì)列里添加元素的線程,消費(fèi)者是從隊(duì)列里拿元素的線程抒蚜。阻塞隊(duì)列就是生產(chǎn)者存放元素的容器掘鄙,而消費(fèi)者也只從容器里拿元素。

Q3:核心方法

方法\處理方式 拋出異常 返回特殊值 一直阻塞 超時(shí)退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
檢查方法 element() peek() 不可用 不可用

Q4:JAVA中的阻塞隊(duì)列

名稱 含義
ArrayBlockingQueue 數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列(最常用)
LinkedBlockingQueue 鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列(最常用)注意:一定要指定大小
PriorityBlockingQueue 支持優(yōu)先級(jí)排序無界阻塞隊(duì)列嗡髓。默認(rèn)自然升序排列
DelayQueue 支持延時(shí)獲取元素的無界阻塞隊(duì)列操漠。
SynchronousQueue 不存儲(chǔ)元素的阻塞隊(duì)列(可以看成是一個(gè)傳球手,負(fù)責(zé)把生產(chǎn)者線程處理的數(shù)據(jù)直接傳遞給消費(fèi)者線程)
LinkedTransferQueue 鏈表結(jié)構(gòu)組成的無界阻塞隊(duì)列
LinkedBlockingDeque 鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列(雙向隊(duì)列指的是可以從隊(duì)列的兩端插入和移出元素)
JAVA中的阻塞隊(duì)列

Q5:實(shí)現(xiàn)原理:

2.4 Android中的線程形態(tài)

2.4.1 AsyncTask

Q1:定義:一種輕量級(jí)的異步任務(wù)類

Android中實(shí)現(xiàn)異步任務(wù)機(jī)制有兩種方式:HandlerAsyncTask

  • Handler機(jī)制存在的問題:代碼相對(duì)臃腫颅夺;多任務(wù)同時(shí)執(zhí)行時(shí)不易精確控制線程。
  • 引入AsyncTask好處:創(chuàng)建異步任務(wù)更簡(jiǎn)單蛹稍,直接繼承它可方便實(shí)現(xiàn)后臺(tái)異步任務(wù)的執(zhí)行和進(jìn)度的回調(diào)更新UI吧黄,而無需編寫任務(wù)線程和Handler實(shí)例就能完成相同的任務(wù)。

Q2:五個(gè)核心方法:

方法 運(yùn)行線程 調(diào)用時(shí)刻 作用
onPreExecute() 主線程 在異步任務(wù)執(zhí)行之前被調(diào)用 可用于進(jìn)行一些界面上的初始化操作
doInBackground() 子線程 異步任務(wù)執(zhí)行時(shí) 可用于處理所有的耗時(shí)任務(wù)唆姐。若需要更新UI需調(diào)用 publishProgress()
onProgressUpdate() 主線程 調(diào)用publishProgress()之后 可利用方法中攜帶的參數(shù)如Progress來對(duì)UI進(jìn)行相應(yīng)地更新
onPostExecute() 主線程 在異步任務(wù)執(zhí)行完畢并通過return語句返回時(shí)被調(diào)用 可利用方法中返回的數(shù)據(jù)來進(jìn)行一些UI操作
onCancelled() 主線程 當(dāng)異步任務(wù)被取消時(shí)被調(diào)用 可用于做界面取消的更新

注意:

  • 不要直接調(diào)用上述方法
  • AsyncTask對(duì)象必須在主線程創(chuàng)建

Q3:開始和結(jié)束異步任務(wù)的方法

  • execute()
  • 必須在主線程中調(diào)用
  • 作用:表示開始一個(gè)異步任務(wù)
  • 注意:一個(gè)異步對(duì)象只能調(diào)用一次execute()方法
  • cancel()
  • 必須在主線程中調(diào)用
  • 作用:表示停止一個(gè)異步任務(wù)

Q4:工作原理:

  • 內(nèi)部有一個(gè)靜態(tài)的Handler對(duì)象即InternalHandler
  • 作用:將執(zhí)行環(huán)境從線程池切換到主線程拗慨;通過它來發(fā)送任務(wù)執(zhí)行的進(jìn)度以及執(zhí)行結(jié)束等消息

  • 注意:必須在主線程中創(chuàng)建

  • 內(nèi)部有兩個(gè)線程池:
  • SerialExecutor:用于任務(wù)的排隊(duì),默認(rèn)是串行的線程池
  • THREAD_POOL_EXECUTOR:用于真正執(zhí)行任務(wù)
  • 排隊(duì)執(zhí)行過程:
  • 把參數(shù)Params封裝為FutureTask對(duì)象奉芦,相當(dāng)于Runnable
  • 調(diào)用SerialExecutor.execute()FutureTask插入到任務(wù)隊(duì)列tasks
  • 若沒有正在活動(dòng)的AsyncTask任務(wù)赵抢,則就會(huì)執(zhí)行下一個(gè)AsyncTask任務(wù)。執(zhí)行完畢后會(huì)繼續(xù)執(zhí)行其他任務(wù)直到所有任務(wù)都完成声功。即默認(rèn)使用串行方式執(zhí)行任務(wù)烦却。

執(zhí)行流程圖:

AsyncTask工作原理

注意AsyncTask不適用于進(jìn)行特別耗時(shí)的后臺(tái)任務(wù),而是建議用線程池

如果想要了解具體源碼的讀者先巴,筆者推薦一篇文章:Android AsyncTask完全解析其爵,帶你從源碼的角度徹底理解

2.4.2 HandlerThread

Q1:定義:

  • HandlerThread是一個(gè)線程類,它繼承自Thread
  • 與普通Thread的區(qū)別:具有消息循環(huán)的效果伸蚯。原理:
  • 內(nèi)部HandlerThread.run()方法中有Looper摩渺,通過Looper.prepare()來創(chuàng)建消息隊(duì)列,并通過Looper.loop()來開啟消息循環(huán)

Q2:實(shí)現(xiàn)方法

  • 實(shí)例化一個(gè)HandlerThread對(duì)象剂邮,參數(shù)是該線程的名稱
  • 通過 HandlerThread.start()開啟線程
  • 實(shí)例化一個(gè)Handler并傳入HandlerThread中的Looper對(duì)象摇幻,使得與HandlerThread綁定
  • 利用Handler即可執(zhí)行異步任務(wù)
  • 當(dāng)不需要HandlerThread時(shí),通過HandlerThread.quit()/quitSafely()方法來終止線程的執(zhí)行
private HandlerThread myHandlerThread ;  
private Handler handler ;  
@Override  
protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
   setContentView(R.layout.activity_main);  
   //實(shí)例化HandlerThread
   myHandlerThread = new HandlerThread("myHandler") ;  
   //開啟HandlerThread
   myHandlerThread.start();  
   //將Handler對(duì)象與HandlerThread線程綁定
   handler =new Handler(myHandlerThread.getLooper()){  
       @Override  
        publicvoid handleMessage(Message msg) {  
           super.handleMessage(msg);  
            // 這里接收Handler發(fā)來的消息,運(yùn)行在handler_thread線程中  
            //TODO...  
        }  
    };  
   
   //在主線程給Handler發(fā)送消息  
   handler.sendEmptyMessage(1) ;  
   new Thread(new Runnable() {  
       @Override  
        publicvoid run() {  
           //在子線程給Handler發(fā)送數(shù)據(jù)  
           handler.sendEmptyMessage(2) ;  
        }  
    }).start();  
}  
@Override  
protected void onDestroy() {  
   super.onDestroy();  
   //終止HandlerThread運(yùn)行
   myHandlerThread.quit() ;  
}  

Q3:用途

  • 進(jìn)行串行異步通信
  • 構(gòu)造IntentService
  • 方便實(shí)現(xiàn)在子線程與子線程直接的通信

Q4:原理:

  • 實(shí)際就是HandlerThread.run()里面封裝了Looper.prepare()Looper.loop()绰姻,以便能在子線程中使用Handler
  • 同時(shí)枉侧,HandlerThread.getLooper()中使用了wait()synchronized代碼塊,當(dāng)Looper==NULL的時(shí)候狂芋,鎖住了當(dāng)前的對(duì)象棵逊,那什么時(shí)候喚醒等待呢?當(dāng)然是在初始化完該線程關(guān)聯(lián)Looper對(duì)象的地方银酗,也就是run()

想了解源碼的話辆影,筆者推薦一篇文章:淺析HandlerThread

2.4.3 IntentService

Q1:定義:

IntentService是一個(gè)繼承自Service的抽象類

Q2:優(yōu)點(diǎn):

  • 相比于線程:由于是服務(wù),優(yōu)先級(jí)比線程高黍特,更不容易被系統(tǒng)殺死蛙讥。因此較適合執(zhí)行一些高優(yōu)先級(jí)的后臺(tái)任務(wù)
  • 相比于普通Service:可自動(dòng)創(chuàng)建子線程來執(zhí)行任務(wù),且任務(wù)執(zhí)行完畢后自動(dòng)退出

Q3:使用方法

  • 新建類并繼承IntentService灭衷,重寫onHandleIntent()次慢,該方法:
  • 運(yùn)行在子線程,因此可以進(jìn)行一些耗時(shí)操作
  • 作用:從Intent參數(shù)中區(qū)分具體的任務(wù)并執(zhí)行這些任務(wù)
  • 在配置文件中進(jìn)行注冊(cè)
  • 在活動(dòng)中利用Intent實(shí)現(xiàn)IntentService的啟動(dòng):
Intent intent = new Intent(this, MyService.class);
intent.putExtra("xxx",xxx);  
startService(intent);//啟動(dòng)服務(wù)

注意:無需手動(dòng)停止服務(wù)翔曲,onHandleIntent()執(zhí)行結(jié)束之后迫像,IntentService會(huì)自動(dòng)停止。

Q4:工作原理

  • IntentService.onCreate()里創(chuàng)建一個(gè)Thread對(duì)象即HandlerThread瞳遍,利用其內(nèi)部的Looper會(huì)實(shí)例化一個(gè)ServiceHandler
  • 任務(wù)請(qǐng)求的Intent會(huì)被封裝到Message并通過ServiceHandler發(fā)送給LooperMessageQueue闻妓,最終在HandlerThread中執(zhí)行
  • ServiceHandler.handleMessage()中會(huì)調(diào)用IntentService.onHandleIntent(),可在該方法中處理后臺(tái)任務(wù)的邏輯,執(zhí)行完畢后會(huì)調(diào)用stopSelf()掠械,以實(shí)現(xiàn)自動(dòng)停止
總體流程圖

下面繼續(xù)來研究下:將Intent 傳遞給服務(wù) & 依次插入到工作隊(duì)列中的流程

Intent傳遞流程

如果對(duì)IntentService的具體源碼感興趣的話由缆,筆者推薦一篇文章:Android多線程:IntentService用法&源碼分析

2.5 線程池

Q1:優(yōu)點(diǎn)

  • 重用線程池中的線程,避免線程的創(chuàng)建和銷毀帶來的性能消耗
  • 有效控制線程池的最大并發(fā)數(shù)猾蒂,避免大量的線程之間因互相搶占系統(tǒng)資源而導(dǎo)致阻塞現(xiàn)象
  • 進(jìn)行線程管理均唉,提供定時(shí)/循環(huán)間隔執(zhí)行等功能

Q2:構(gòu)造方法分析

  • 線程池的概念來源:Java中的Executor,它是一個(gè)接口
  • 線程池的真正實(shí)現(xiàn):ThreadPoolExecutor肚菠,提供一系列參數(shù)來配置線程池
//構(gòu)造參數(shù)
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
  • corePoolSize:核心線程數(shù)
  • 默認(rèn)情況下舔箭,核心線程會(huì)在線程中一直存活

  • 當(dāng)設(shè)置ThreadPoolExecutorallowCoreThreadTimeOut屬性為

    A.true:表示核心線程閑置超過超時(shí)時(shí)長,會(huì)被回收

    B.false: 表示核心線程不會(huì)被回收蚊逢,會(huì)在線程池中一直存活

  • maximumPoolSize:最大線程數(shù)

當(dāng)活動(dòng)線程數(shù)達(dá)到這個(gè)數(shù)值后层扶,后續(xù)的任務(wù)將會(huì)被阻塞

  • keepAliveTime:非核心線程超時(shí)時(shí)間
  • 超過這個(gè)時(shí)長,閑置的非核心線程就會(huì)被回收
  • 當(dāng)設(shè)置ThreadPoolExecutorallowCoreThreadTimeTout屬性為true時(shí)时捌,keepAliveTime對(duì)核心線程同樣有效
  • unit:用于指定keepAliveTime參數(shù)的時(shí)間單位

單位有:TimeUnit.MILLISECONDS怒医、TimeUnit.SECONDS炉抒、TimeUnit.MINUTES等奢讨;

  • workQueue:任務(wù)隊(duì)列

通過線程池的execute()方法提交的Runnable對(duì)象會(huì)存儲(chǔ)在這個(gè)參數(shù)中

  • threadFactory:線程工廠,可創(chuàng)建新線程

一個(gè)接口,只有一個(gè)方法Thread newThread(Runnable r)

  • handler:在線程池?zé)o法執(zhí)行新任務(wù)時(shí)進(jìn)行調(diào)度

Q3:ThreadPoolExecutor的默認(rèn)工作策略

處理流程

? Q4:線程池的分類

名稱 含義 特點(diǎn)
FixThreadPool 線程數(shù)量固定的線程池拿诸,所有線程都是核心線程扒袖,當(dāng)線程空閑時(shí)不會(huì)被回收 快速響應(yīng)外界請(qǐng)求
CachedThreadPool 線程數(shù)量不定的線程池(最大線程數(shù)為Integer.MAX_VALUE),只有非核心線程亩码,空閑線程有超時(shí)機(jī)制季率,超時(shí)回收 適合于執(zhí)行大量的耗時(shí)較少的任務(wù)
ScheduledThreadPool 核心線程數(shù)量固定,非核心線程數(shù)量不定 定時(shí)任務(wù)和固定周期的任務(wù)
SingleThreadExecutor 只有一個(gè)核心線程描沟,可確保所有的任務(wù)都在同一個(gè)線程中按順序執(zhí)行 無需處理線程同步問題

三.再聊聊AsyTask的不足

AsyncTask 看似十分美好飒泻,但實(shí)際上存在著非常多的不足,這些不足使得它逐漸退出了歷史舞臺(tái)吏廉,因此如今已經(jīng)被 RxJava泞遗、協(xié)程等新興框架所取代(PS:有機(jī)會(huì)希望能和大家一起探究下RxJava的源碼)

  • 生命周期

AsyncTask 沒有與 ActivityFragment 的生命周期綁定席覆,即使 Activity 被銷毀史辙,它的 doInBackground 任務(wù)仍然會(huì)繼續(xù)執(zhí)行

  • 取消任務(wù)

AsyncTaskcancel 方法的參數(shù) mayInterruptIfRunning 存在的意義不大,并且它無法保證任務(wù)一定能取消佩伤,只能盡快讓任務(wù)取消(比如如果正在進(jìn)行一些無法打斷的操作時(shí)聊倔,任務(wù)就仍然會(huì)運(yùn)行)

  • 內(nèi)存泄漏
  • 由于它沒有與 Activity 等生命周期進(jìn)行綁定,因此它的生命周期仍然可能比 Activity
  • 如果將它作為 Activity 的非 static 內(nèi)部類生巡,則它會(huì)持有 Activity 的引用耙蔑,導(dǎo)致 Activity 的內(nèi)存無法釋放。(PS:與 Handler的內(nèi)存泄漏問題類似孤荣,參考文章:進(jìn)階之路 | 奇妙的Handler之旅
  • 并行/串行

由于 AsyncTask 的串行和并行執(zhí)行在多個(gè)版本上都進(jìn)行了修改纵潦,所以當(dāng)多個(gè) AsyncTask 依次執(zhí)行時(shí),它究竟是串行還是并行執(zhí)行取決于用戶手機(jī)的版本垃环。具體修改如下:

A.Android 1.6 之前:各個(gè) AsyncTask 按串行的順序進(jìn)行執(zhí)行

B.Android 1.6--Android 3.0 :由于設(shè)計(jì)者認(rèn)為串行執(zhí)行效率太低邀层,因此改為了并行執(zhí)行,最多五個(gè) AsyncTask 同時(shí)執(zhí)行

C.Android 3.0 之后:由于之前的改動(dòng)遂庄,很多應(yīng)用出現(xiàn)了并發(fā)問題寥院,因此引入 SerialExecutor 改回了串行執(zhí)行,但對(duì)并行執(zhí)行進(jìn)行了支持


如果文章對(duì)您有一點(diǎn)幫助的話涛目,希望您能點(diǎn)一下贊秸谢,您的點(diǎn)贊,是我前進(jìn)的動(dòng)力

本文參考鏈接:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市沫换,隨后出現(xiàn)的幾起案子臭蚁,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件垮兑,死亡現(xiàn)場(chǎng)離奇詭異冷尉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)系枪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門雀哨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人私爷,你說我怎么就攤上這事雾棺。” “怎么了衬浑?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵垢村,是天一觀的道長。 經(jīng)常有香客問我嚎卫,道長嘉栓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任拓诸,我火速辦了婚禮侵佃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘奠支。我一直安慰自己馋辈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布倍谜。 她就那樣靜靜地躺著迈螟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪尔崔。 梳的紋絲不亂的頭發(fā)上答毫,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音季春,去河邊找鬼洗搂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛载弄,可吹牛的內(nèi)容都是我干的耘拇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼宇攻,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼惫叛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逞刷,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤嘉涌,失蹤者是張志新(化名)和其女友劉穎妻熊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體洛心,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年题篷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了词身。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡番枚,死狀恐怖法严,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情葫笼,我是刑警寧澤深啤,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站路星,受9級(jí)特大地震影響溯街,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洋丐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一呈昔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧友绝,春花似錦堤尾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至掷漱,卻和暖如春粘室,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卜范。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來泰國打工育特, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人先朦。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓缰冤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親喳魏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子棉浸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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