標籤:

Android中的Thread與AsyncTask的區別?

這個問題百度,google了下,看了些文章,希望在知乎能得到更詳盡答案.

--------------------------------------------------------------------------------------

Chaos同學給的鏈接Android消息處理機制:源碼剖析Handler、Looper,並實現圖片非同步載入


謝邀。

Android 原生的 AsyncTask.java 是對線程池的一個封裝,使用其自定義的 Executor 來調度線程的執行方式(並發還是串列),並使用 Handler 來完成子線程和主線程數據的共享。

預先了解 AsyncTask,必先對線程池有所了解。

一般情況下,如果使用子線程去執行一些任務,那麼使用 new Thread 的方式會很方便的創建一個線程,如果涉及到主線程和子線程的通信,我們將使用 Handler(一般需要刷新 UI 的適合用到)。

如果我們創建大量的(特別是在短時間內,持續的創建生命周期較長的線程)野生線程,往往會出現如下兩方面的問題:

  1. 每個線程的創建與銷毀(特別是創建)的資源開銷是非常大的;
  2. 大量的子線程會分享主線程的系統資源,從而會使主線程因資源受限而導致應用性能降低。

各位開發一線的前輩們為了解決這個問題,引入了線程池(ThreadPool)的概念,也就是把這些野生的線程圈養起來,統一的管理他們。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。

那麼線程池是如何使用的呢?

我們可以通過ThreadPoolExecutor來創建一個線程池。

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler);

創建一個線程池需要輸入幾個參數:

  • corePoolSize(線程池的基本大小):當提交一個任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閑的基本線程能夠執行新任務也會創建線程,等到需要執行的任務數大於線程池基本大小時就不再創建。如果調用了線程池的 prestartAllCoreThreads 方法,線程池會提前創建並啟動所有基本線程。
  • runnableTaskQueue(任務隊列):用於保存等待執行的任務的阻塞隊列。 可以選擇以下幾個阻塞隊列。
    • ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按 FIFO(先進先出)原則對元素進行排序。
    • LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按 FIFO (先進先出) 排序元素,吞吐量通常要高於 ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool() 使用了這個隊列。
    • SynchronousQueue:一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQueue,靜態工廠方法 Executors.newCachedThreadPool 使用了這個隊列。
    • PriorityBlockingQueue:一個具有優先順序的無限阻塞隊列。
  • maximumPoolSize(線程池最大大小):線程池允許創建的最大線程數。如果隊列滿了,並且已創建的線程數小於最大線程數,則線程池會再創建新的線程執行任務。值得注意的是如果使用了無界的任務隊列這個參數就沒什麼效果。
  • ThreadFactory:用於設置創建線程的工廠,可以通過線程工廠給每個創建出來的線程設置更有意義的名字。
  • RejectedExecutionHandler(飽和策略):當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採取一種策略處理提交的新任務。這個策略默認情況下是 AbortPolicy,表示無法處理新任務時拋出異常。以下是JDK1.5提供的四種策略。
    • AbortPolicy:直接拋出異常。
    • CallerRunsPolicy:只用調用者所在線程來運行任務。
    • DiscardOldestPolicy:丟棄隊列里最近的一個任務,並執行當前任務。
    • DiscardPolicy:不處理,丟棄掉。
    • 當然也可以根據應用場景需要來實現 RejectedExecutionHandler 介面自定義策略。如記錄日誌或持久化不能處理的任務。
  • keepAliveTime(線程活動保持時間):線程池的工作線程空閑後,保持存活的時間。所以如果任務很多,並且每個任務執行的時間比較短,可以調大這個時間,提高線程的利用率。
  • TimeUnit(線程活動保持時間的單位):可選的單位有天(DAYS),小時(HOURS),分鐘(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

如何向線程池提交線程任務呢?

  1. 我們可以使用線程池的 execute 提交的任務,但是 execute 方法沒有返回值,所以無法判斷任務是否被線程池執行成功:

threadsPool.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
});

2. 我們也可以使用 submit 方法來提交任務,它會返回一個 future,那麼我們可以通過這個 future 來判斷任務是否執行成功,通過 futureget 方法來獲取返回值,get 方法會阻塞住直到任務完成,而使用 get(long timeout, TimeUnit unit) 方法則會阻塞一段時間後立即返回,這時有可能任務沒有執行完:

Future& future = executor.submit(harReturnValuetask);
try {
Object s = future.get();
} catch (InterruptedException e) {
// 處理中斷異常
} catch (ExecutionException e) {
// 處理無法執行任務異常
} finally {
// 關閉線程池
executor.shutdown();
}

線程池是如何關閉的呢?

ThreadPoolExecutor 提供了兩個方法,用於線程池的關閉,分別是 shutdown()shutdownNow(),其中:

  • shutdown():不會立即終止線程池,而是要等所有任務緩存隊列中的任務都執行完後才終止,但再也不會接受新的任務;
  • shutdownNow():立即終止線程池,並嘗試打斷正在執行的任務,並且清空任務緩存隊列,返回尚未執行的任務。

線程池的原理?

線程池中比較重要的規則:

  • corePoolSize 與 maximumPoolSize

由於 ThreadPoolExecutor 將根據 corePoolSize maximumPoolSize 設置的邊界自動調整池大小,當新任務在方法 execute(java.lang.Runnable) 中提交時:

    1. 如果運行的線程少於 corePoolSize,則創建新線程來處理請求,即使其他輔助線程是空閑的;
    2. 如果設置的 corePoolSizemaximumPoolSize 相同,則創建的線程池是大小固定的,如果運行的線程數與 corePoolSize 相同,當有新請求過來時,若 workQueue 未滿,則將請求放入 workQueue 中,等待有空閑的線程去從 workQueue 中取任務並處理
    3. 如果運行的線程多於 corePoolSize 而少於 maximumPoolSize,則僅當隊列滿時才創建新線程去處理請求;
    4. 如果運行的線程多於 corePoolSize 並且等於 maximumPoolSize,若隊列已經滿了,則通過RejectedExecutionHandler 所指定的策略來處理新請求;
    5. 如果將 maximumPoolSize 設置為基本的無界值(如 Integer.MAX_VALUE),則允許池適應任意數量的並發任務

也就是說,處理任務的優先順序為:

    • corePoolSize &> workQueue &> maximumPoolSize,如果三者都滿了,使用 RejectedExecutionHandler 處理被拒絕的任務。
    • 當池中的線程數大於 corePoolSize 的時候,多餘的線程會等待 keepAliveTime 長的時間,如果無請求可處理就自行銷毀。

  • workQueue線程池所使用的緩衝隊列,該緩衝隊列的長度決定了能夠緩衝的最大數量,緩衝隊列有三種通用策略:

    1. 直接提交。工作隊列的默認選項是 SynchronousQueue,它將任務直接提交給線程而不保持它們。在此,如果不存在可用於立即運行任務的線程,則試圖把任務加入隊列將失敗,因此會構造一個新的線程。此策略可以避免在處理可能具有內部依賴性的請求集時出現鎖。直接提交通常要求無界 maximumPoolSizes 以避免拒絕新提交的任務。當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性;
    2. 無界隊列。使用無界隊列(例如,不具有預定義容量的 LinkedBlockingQueue)將導致在所有 corePoolSize 線程都忙時新任務在隊列中等待。這樣,創建的線程就不會超過 corePoolSize(因此,maximumPoolSize 的值也就無效了)。當每個任務完全獨立於其他任務,即任務執行互不影響時,適合於使用無界隊列;例如,在 Web 頁伺服器中。這種排隊可用於處理瞬態突發請求,當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性;
    3. 有界隊列。當使用有限的 maximumPoolSizes 時,有界隊列(如 ArrayBlockingQueue)有助於防止資源耗盡,但是可能較難調整和控制。隊列大小和最大池大小可能需要相互折衷:使用大型隊列和小型池可以最大限度地降低 CPU 使用率、操作系統資源和上下文切換開銷,但是可能導致人工降低吞吐量。如果任務頻繁阻塞(例如,如果它們是 I/O 邊界),則系統可能為超過您許可的更多線程安排時間。使用小型隊列通常要求較大的池大小,CPU 使用率較高,但是可能遇到不可接受的調度開銷,這樣也會降低吞吐量。

  • ThreadFactory

使用 ThreadFactory 創建新線程。如果沒有另外說明,則在同一個 ThreadGroup 中一律使用 Executors.defaultThreadFactory() 創建線程,並且這些線程具有相同的 NORM_PRIORITY 優先順序和非守護進程狀態。通過提供不同的 ThreadFactory,可以改變線程的名稱、線程組、優先順序、守護進程狀態等等。如果執行 newThreadThreadFactory 未能創建線程(返回 null),則執行程序將繼續運行,但不能執行任何任務。

接下來我們看一下 ThreadPoolExecutor 中最重要的 execute 方法:

public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//如果線程數小於基本線程數,則創建線程並執行當前任務
if (poolSize &>= corePoolSize || !addIfUnderCorePoolSize(command)) {
//如線程數大於等於基本線程數或線程創建失敗,則將當前任務放到工作隊列中。
if (runState == RUNNING workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
//如果線程池不處於運行中或任務無法放入隊列,並且當前線程數量小於最大允許的線程數量,
則創建一個線程執行任務。
else if (!addIfUnderMaximumPoolSize(command))
//拋出RejectedExecutionException異常
reject(command); // is shutdown or saturated
}
}

線程池容量的動態調整?

ThreadPoolExecutor 提供了動態調整線程池容量大小的方法:setCorePoolSize() setMaximumPoolSize():

  • setCorePoolSize:設置核心池大小
  • setMaximumPoolSize:設置線程池最大能創建的線程數目大小

當上述參數從小變大時,ThreadPoolExecutor 進行線程賦值,還可能立即創建新的線程來執行任務。

線程池的監控?

通過線程池提供的參數進行監控。線程池裡有一些屬性在監控線程池的時候可以使用

  • taskCount:線程池需要執行的任務數量。
  • completedTaskCount:線程池在運行過程中已完成的任務數量。小於或等於 taskCount
  • largestPoolSize:線程池曾經創建過的最大線程數量。通過這個數據可以知道線程池是否滿過。如等於線程池的最大大小,則表示線程池曾經滿了。
  • getPoolSize: 線程池的線程數量。如果線程池不銷毀的話,池裡的線程不會自動銷毀,所以這個大小隻增不減。
  • getActiveCount:獲取活動的線程數。

通過擴展線程池進行監控。通過繼承線程池並重寫線程池的 beforeExecuteafterExecute terminated 方法,我們可以在任務執行前,執行後和線程池關閉前干一些事情。如監控任務的平均執行時間,最大執行時間和最小執行時間等。

使用線程池的風險?

雖然線程池是構建多線程應用程序的強大機制,但使用它並不是沒有風險的。用線程池構建的應用程序容易遭受任何其它多線程應用程序容易遭受的所有並發風險,諸如同步錯誤和死鎖,它還容易遭受特定於線程池的少數其它風險,諸如與池有關的死鎖、資源不足和線程泄漏。

  • 死鎖

任何多線程應用程序都有死鎖風險。當一組進程或線程中的每一個都在等待一個只有該組中另一個進程才能引起的事件時,我們就說這組進程或線程 死鎖了。死鎖的最簡單情形是:線程 A 持有對象 X 的獨佔鎖,並且在等待對象 Y 的鎖,而線程 B 持有對象 Y 的獨佔鎖,卻在等待對象 X 的鎖。除非有某種方法來打破對鎖的等待(Java 鎖定不支持這種方法),否則死鎖的線程將永遠等下去。

雖然任何多線程程序中都有死鎖的風險,但線程池卻引入了另一種死鎖可能,在那種情況下,所有池線程都在執行已阻塞的等待隊列中另一任務的執行結果的任務,但這一任務卻因為沒有未被佔用的線程而不能運行。當線程池被用來實現涉及許多交互對象的模擬,被模擬的對象可以相互發送查詢,這些查詢接下來作為排隊的任務執行,查詢對象又同步等待著響應時,會發生這種情況。

  • 資源不足

線程池的一個優點在於:相對於其它替代調度機制(有些我們已經討論過)而言,它們通常執行得很好。但只有恰當地調整了線程池大小時才是這樣的。線程消耗包括內存和其它系統資源在內的大量資源。除了 Thread 對象所需的內存之外,每個線程都需要兩個可能很大的執行調用堆棧。除此以外,JVM 可能會為每個 Java 線程創建一個本機線程,這些本機線程將消耗額外的系統資源。最後,雖然線程之間切換的調度開銷很小,但如果有很多線程,環境切換也可能嚴重地影響程序的性能。

如果線程池太大,那麼被那些線程消耗的資源可能嚴重地影響系統性能。在線程之間進行切換將會浪費時間,而且使用超出比您實際需要的線程可能會引起資源匱乏問題,因為池線程正在消耗一些資源,而這些資源可能會被其它任務更有效地利用。除了線程自身所使用的資源以外,服務請求時所做的工作可能需要其它資源,例如 JDBC 連接、套接字或文件。這些也都是有限資源,有太多的並發請求也可能引起失效,例如不能分配 JDBC 連接。

  • 並發錯誤

線程池和其它排隊機制依靠使用 wait() 和 notify() 方法,這兩個方法都難於使用。如果編碼不正確,那麼可能丟失通知,導致線程保持空閑狀態,儘管隊列中有工作要處理。使用這些方法時,必須格外小心;即便是專家也可能在它們上面出錯。而最好使用現有的、已經知道能工作的實現,例如 util.concurrent 包。

  • 線程泄漏

各種類型的線程池中一個嚴重的風險是線程泄漏,當從池中除去一個線程以執行一項任務,而在任務完成後該線程卻沒有返回池時,會發生這種情況。發生線程泄漏的一種情形出現在任務拋出一個 RuntimeException 或一個 Error 時。如果池類沒有捕捉到它們,那麼線程只會退出而線程池的大小將會永久減少一個。當這種情況發生的次數足夠多時,線程池最終就為空,而且系統將停止,因為沒有可用的線程來處理任務。

有些任務可能會永遠等待某些資源或來自用戶的輸入,而這些資源又不能保證變得可用,用戶可能也已經回家了,諸如此類的任務會永久停止,而這些停止的任務也會引起和線程泄漏同樣的問題。如果某個線程被這樣一個任務永久地消耗著,那麼它實際上就被從池除去了。對於這樣的任務,應該要麼只給予它們自己的線程,要麼只讓它們等待有限的時間。

  • 請求過載

僅僅是請求就壓垮了伺服器,這種情況是可能的。在這種情形下,我們可能不想將每個到來的請求都排隊到我們的工作隊列,因為排在隊列中等待執行的任務可能會消耗太多的系統資源並引起資源缺乏。在這種情形下決定如何做取決於您自己;在某些情況下,您可以簡單地拋棄請求,依靠更高級別的協議稍後重試請求,您也可以用一個指出伺服器暫時很忙的響應來拒絕請求。

參考資料:

Java 7之多線程線程池

聊聊並發(三)——JAVA線程池的分析和使用

Java 理論與實踐: 線程池與工作隊列

以上。

預祝題主61兒童節快樂。


有關AsyncTask的知識 @肥肥魚 已經說的很好啦,我再補充一些知識吧~

本質上來說,AsyncTask就是用於解決非同步處理任務的類,而它的內部實現是Thread+Handler的組合,題主可能會問了,那肥肥魚大大說的線程池為啥也被引入AsyncTask了呢?主要原因在於,我們在非同步處理任務的時候可能需要進行多線程非同步處理,那麼每次都要手動去創建線程,以及手動管理這些線程都會非常麻煩,而Java在解決並發編程問題時提出的線程池類則能很好地解決這類問題,於是AsyncTask里就有了這個類了。

如果題主想要了解更多的知識,可以看我寫的一篇博文,解析Android消息處理機制

http://blog.csdn.net/u012403246/article/details/45949963


前天看了一下AsyncTask的源碼,很詳細,你看了就明白了


一張流,簡單直接。 非原創。

原帖地址:Difference between Android Service,Thread,IntentService and AsyncTask


為了回答這個問題,周末讀代碼到十二點,這是什麼精神?

答案在我的blog中,由於知乎不支持markdown,請移步一觀:

Android中的Handler和AsyncTask的區別


簡單說來asynctask就是thread+handler+線程池。

線程池有最多128總任務數 。

執行任務數量不同版本不一樣。。有版本是最多5個同時執行。有的版本是核數+1。。。


推薦閱讀:

Android應用怎麼繞過Fiddler等抓包工具?
各位互聯網行業的大大們,你認識的最牛的安卓開發者是怎樣的?
Android 已發行多年,移動 App 已經趨近飽和,那麼 Android 開發還會有那麼吃香嗎?
Android 開發中,有哪些坑需要注意?

TAG:Android開發 |