天天看點

Android線程與線程池

在Android中,幾乎完全采用了Java中的線程機制。線程是最小的排程機關,在很多情況下為了使APP更加流程地運作,我們不可能将很多事情都放在主線程上執行,這樣會造成嚴重卡頓(ANR),那麼這些事情應該交給子線程去做,但對于一個系統而言,建立、銷毀、排程線程的過程是需要開銷的,是以我們并不能無限量地開啟線程,那麼對線程的了解就變得尤為重要了。

一般實作線程的方法有兩種,一種是類繼承Thread,一種是實作接口Runnable。這兩種方式的優缺點如何呢?我們知道Java是單繼承但可以調用多個接口,是以看起來Runnable更加好一些。

繼承Thread

實作Runnable接口

當我們調用Thread時,會有兩種方式:

我們應該知道,run()方法隻是調用了Thread執行個體的run()方法而已,它仍然運作在主線程上,而start()方法會開辟一個新的線程,在新的線程上調用run()方法,此時它運作在新的線程上。

Runnable隻是一個接口,是以單看這個接口它和線程毫無瓜葛,可能一部分人會以為Runnable實作了線程,這種了解是不對的。Thread調用了Runnable接口中的方法用來線上程中執行任務。

Runnable 和 Callable 都代表那些要在不同的線程中執行的任務。Runnable 從 JDK1.0 開始就有了,Callable 是在 JDK1.5 增加的。它們的主要差別是 Callable 的 call() 方法可以傳回值和抛出異常,而 Runnable 的 run() 方法沒有這些功能。Callable 可以傳回裝載有計算結果的 Future 對象。

我們通過對比兩個接口得到這樣的結論:

Callable 接口下的方法是 call(),Runnable 接口的方法是 run();

Callable 的任務執行後可傳回值,而 Runnable 的任務是不能傳回值的;

call() 方法可以抛出異常,run()方法不可以的;

運作 Callable 任務可以拿到一個 Future 對象,表示異步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,并檢索計算的結果。通過 Future 對象可以了解任務執行情況,可取消任務的執行,還可擷取執行結果;

然而…Thread類隻支援Runnable接口,由此引入FutureTask的概念。

FutureTask 實作了 Runnable 和 Future,是以兼顧兩者優點,既可以在 Thread 中使用,又可以在 ExecutorService 中使用。

使用 FutureTask 的好處是 FutureTask 是為了彌補 Thread 的不足而設計的,它可以讓程式員準确地知道線程什麼時候執行完成并獲得到線程執行完成後傳回的結果。FutureTask 是一種可以取消的異步的計算任務,它的計算是通過 Callable 實作的,它等價于可以攜帶結果的 Runnable,并且有三個狀态:等待、運作和完成。完成包括所有計算以任意的方式結束,包括正常結束、取消和異常。

除了以上這些,在Android中充當線程的角色還有AsyncTask、HandlerThread、IntentService。它們本質上都是由Handler+Thread來構成的,不過不同的設計讓它們可以在不同的場合發揮更好的作用。我們來簡單地說一下它們各自的特點:

AsyncTask,它封裝了線程池和Handler,主要為我們在子線程中更新UI提供便利。 

HandlerThread,它是個具有消息隊列的線程,可以友善我們在子線程中處理不同的事務。 

IntentService,我們可以将它看做為HandlerThread的更新版,它是服務,優先級更高。

下面我來通過源碼,用法等來認清這些線程。

AsyncTask是一個輕量級的異步任務類,它可以線上程池中執行背景任務,然後把執行的進度和結果傳遞給主線程并且在主線程中更新UI。

1. 用法

AsyncTask是一個抽象泛型類,聲明:<code>public abstract class AsyncTask&lt;Params, Progress, Result&gt;;</code> 

并且提供了4個核心方法。

參數1,Params,異步任務的入參;

參數2,Progress,執行任務的進度;

參數3,Result,背景任務執行的結果;

方法1, onPreExecute(),在主線程中執行,任務開啟前的準備工作;

方法2,doInbackground(Params…params),開啟子線程執行背景任務;

方法3,onProgressUpdate(Progress values),在主線程中執行,更新UI進度;

方法4,onPostExecute(Result result),在主線程中執行,異步任務執行完成後執行,它的參數是doInbackground()的傳回值。

從上面我們可以清晰地了解到AsyncTask的具體用法,但是為什麼它是如此表現呢?或者說,為什麼它是這樣而非其它樣,那麼,我們就來看看源碼吧。

2. 源碼分析

我們知道開啟AsyncTask異步任務是通過new MyAsyncTask().execute()開啟的,我們就以此為入口開始分析。

2.1 execute()方法

分析:從源碼中,可以看到execute()方法調用了executeOnExecutor()方法。

2.2 executeOnExecutor()方法

我們可以看到它用來實作AsyncTask的排隊執行,即AsyncTask是串行而非并發執行的。源碼的大緻執行過程我們會在下面給出流程圖。

2.3 mWork的call()方法

我們可以看到此時開始執行AsyncTask的核心方法2,我當然也是需要我們自己實作的。

2.4 sHandler

我們在2.3中可以看到最後執行到了postResult()方法,此方法就是利用一個靜态sHandler變量将消息發送出去并且交由主線程處理,這樣一來就實作了子線程和主線程的切換問題。不僅僅是此處由sHandler處理,當在子線程中執行doInbackground()時,如果我們需要更新進度即調用核心方法3也需要利用sHandler發送消息給主線程處理。

2.5 finish

我們可以通過這樣的調用來驗證其是串行還是并發的。

可以看到這三個異步任務的執行時間是

由此可知它的執行是串行的,如果需要并發執行,調用executeOnExecutor()即可。

看時間是這樣的:

我們可以看到建立Service時,實作了一個HandlerThread的執行個體開啟了一個線程,并線上程内部進行消息輪詢,又建立了一個Handler來收發Looper的消息。

我們每啟動一次服務時,不會開啟新的服務,隻是會調用onStartCommand()函數,我們又看到該函數調用了onStart()方法。

在該方法中我們看到,這裡用來接收Context傳遞的參數,通過Handler發送出去,然後再HandlerThread的線程上接收消息并且處理。

我們可以看到onHandleIntent()方法是我們需要接收消息處理的。

流程如下

Android線程與線程池

我們在上面的篇幅中,講到了線程的概念以及一些擴充線程。那麼我們考慮一個問題,我如果需要同時做很多事情,是不是給每一個事件都開啟一個線程呢?那如果我的事件無限多呢?頻繁地建立/銷毀線程,CPU該吃不消了吧。是以,這時候線程池的概念就來了。我們舉個例子來闡述一下線程池大緻工作原理。

比如,有個老闆戚總開了個飯店,每到中午就有很多人點外賣,一開始戚總招了10個人送外賣,然而由于午飯高峰期可能同時需要派送50份外賣,那如何保證高效地運作呢?

戚總想着那再招40個員工送?我去,那我這店豈不是要賠死,人員工資這麼高,并且大部分時候也隻需要同時派送幾份外賣而已,招這麼多人幹瞪眼啊,是啊。但我還得保證高峰期送餐效率,咋辦呢?

經過一番思想鬥争,戚總想通了,我也不可能做到完美,盡量高效就行了,那正常時間一般隻需要同時送四五家外賣,那我就招5個員工作為正式員工(核心線程),再招若幹兼職(非核心線程)在用餐高峰時緩解一下送餐壓力即可。

那麼,人員配置設定方案出來了,當正式員工(核心線程)空閑時有單進來理所應當讓他們派送,如果正式員工忙不過了,就讓兼職人員(非核心線程)送,按單提成唄。

好吧,啰嗦這麼多,這就是線程池的概念原理吧。

我們來總結一下優點吧。

重用線程池中的線程,避免頻繁地建立和銷毀線程帶來的性能消耗;

有效控制線程的最大并發數量,防止線程過大導緻搶占資源造成系統阻塞;

可以對線程進行一定地管理。

ExecutorService是最初的線程池接口,ThreadPoolExecutor類是對線程池的具體實作,它通過構造方法來配置線程池的參數,我們來分析一下它常用的構造函數吧。

參數解釋:

corePoolSize,線程池中核心線程的數量,預設情況下,即使核心線程沒有任務在執行它也存在的,我們固定一定數量的核心線程且它一直存活這樣就避免了一般情況下CPU建立和銷毀線程帶來的開銷。我們如果将ThreadPoolExecutor的allowCoreThreadTimeOut屬性設定為true,那麼閑置的核心線程就會有逾時政策,這個時間由keepAliveTime來設定,即keepAliveTime時間内如果核心線程沒有回應則該線程就會被終止。allowCoreThreadTimeOut預設為false,核心線程沒有逾時時間。

maximumPoolSize,線程池中的最大線程數,當任務數量超過最大線程數時其它任務可能就會被阻塞。最大線程數=核心線程+非核心線程。非核心線程隻有當核心線程不夠用且線程池有空餘時才會被建立,執行完任務後非核心線程會被銷毀。

keepAliveTime,非核心線程的逾時時長,當執行時間超過這個時間時,非核心線程就會被回收。當allowCoreThreadTimeOut設定為true時,此屬性也作用在核心線程上。

unit,枚舉時間機關,TimeUnit。

workQueue,線程池中的任務隊列,我們送出給線程池的runnable會被存儲在這個對象上。

線程池的配置設定遵循這樣的規則:

當線程池中的核心線程數量未達到最大線程數時,啟動一個核心線程去執行任務;

如果線程池中的核心線程數量達到最大線程數時,那麼任務會被插入到任務隊列中排隊等待執行;

如果在上一步驟中任務隊列已滿但是線程池中線程數量未達到限定線程總數,那麼啟動一個非核心線程來處理任務;

如果上一步驟中線程數量達到了限定線程總量,那麼線程池則拒絕執行該任務,且ThreadPoolExecutor會調用RejectedtionHandler的rejectedExecution方法來通知調用者。

我們來介紹一下不同特性的線程池,它們都直接或者間接通過ThreadPoolExecutor來實作自己的功能。它們分别是:

FixedThreadPool

CachedThreadPool

ScheduledThreadPool

SingleThreadExecutor

1. FixedThreadPool

通過Executors的newFixedThreadPool()方法建立,它是個線程數量固定的線程池,該線程池的線程全部為核心線程,它們沒有逾時機制且排隊任務隊列無限制,因為全都是核心線程,是以響應較快,且不用擔心線程會被回收。

參數nThreads,就是我們固定的核心線程數量。

2. CachedThreadPool

通過Executors的newCachedThreadPool()方法來建立,它是一個數量無限多的線程池,它所有的線程都是非核心線程,當有新任務來時如果沒有空閑的線程則直接建立新的線程不會去排隊而直接執行,并且逾時時間都是60s,是以此線程池适合執行大量耗時小的任務。由于設定了逾時時間為60s,是以當線程空閑一定時間時就會被系統回收,是以理論上該線程池不會有占用系統資源的無用線程。

3. ScheduledThreadPool

通過Executors的newScheduledThreadPool()方法來建立,ScheduledThreadPool線程池像是上兩種的合體,它有數量固定的核心線程,且有數量無限多的非核心線程,但是它的非核心線程逾時時間是0s,是以非核心線程一旦空閑立馬就會被回收。這類線程池适合用于執行定時任務和固定周期的重複任務。

參數corePoolSize是核心線程數量。

4. SingleThreadExecutor

通過Executors的newSingleThreadExecutor()方法來建立,它内部隻有一個核心線程,它確定所有任務進來都要排隊按順序執行。它的意義在于,統一所有的外界任務到同一線程中,讓調用者可以忽略線程同步問題。

shutDown(),關閉線程池,需要執行完已送出的任務;

shutDownNow(),關閉線程池,并嘗試結束已送出的任務;

allowCoreThreadTimeOut(boolen),允許核心線程閑置逾時回收;

execute(),送出任務無傳回值;

submit(),送出任務有傳回值;

execute()方法

接收一個Runnable對象作為參數,異步執行。

<code>mExecutor.execute(myRunnable);</code>

<code></code>

<code>本文轉自 一點點征服 部落格園部落格,原文連結:</code>http://www.cnblogs.com/ldq2016/p/8067103.html,如需轉載請自行聯系原作者

繼續閱讀