Android 線程池基礎介紹

1線程池的創建

1.1 線程池的概念

線程池是一種多線程處理形式,處理過程中將任務添加到隊列,然後在創建線程後自動啓動這些任務。線程池線程都是後臺線程.每個線程都使用默認的堆棧大小,以默認的優先級運行,並處於多線程單元中.如果某個線程在託管代碼中空閒(如正在等待某個事件),則線程池將插入另一個輔助線程來使所有處理器保持繁忙.如果所有線程池線程都始終保持繁忙,但隊列中包含掛起的工作,則線程池將在一段時間後創建另一個輔助線程但線程的數目永遠不會超過最大值.超過最大值的線程可以排隊,但他們要等到其他線程完成後才啓動。

1.2 什麼情況下不要使用線程池

●如果需要使一個任務具有特定優先級(例如需要快速響應界面的功能)

●如果具有可能會長時間運行(並因此阻塞其他任務)的任務

●如果需要將線程放置到單線程單元中(線程池中的線程均處於多線程單元中)

●如果需要永久標識來標識和控制線程,比如想使用專用線程來終止該線程,將其        掛起或按名稱發現它

1.3 Android中建立線程池的步驟和線程池種類:

1。定義線程類

class Handler implements Runnable{

}

2。建立ExecutorService線程池

ExecutorService executorService = Executors.newCachedThreadPool(自定義數量);

或者

int cpuNums = Runtime.getRuntime().availableProcessors();

 //獲取當前系統的CPU 數目

ExecutorService executorService =Executors.newFixedThreadPool(cpuNums * POOL_SIZE);

//ExecutorService通常根據系統資源情況靈活定義線程池大小

3。調用線程池操作

循環操作,成爲daemon,把新實例放入Executor池中

      while(true){

        executorService.execute(new Handler(socket));

           // class Handler implements Runnable{

        或者

        executorService.execute(createTask(i));

            //private static Runnable createTask(final int taskID)

      }   

(關於ExecutorServiceexecutesubmit方法,參考:

http://hi.baidu.com/Ʈ��������/blog/item/def1a92541d3851d4d088d4e.html)

execute(Runnable對象)方法

其實就是對Runnable對象調用start()方法

(當然還有一些其他後臺動作,比如隊列,優先級,IDLE timeoutactive激活等)

1.4 線程池的時序圖



1.5 線程池創建的方式

幾種不同的ExecutorService線程池對象 

1.       newCachedThreadPool() 
緩存型池子,先查看池中有沒有以前建立的線程,如果有,就reuse.如果沒有,就建一個新的線程加入池中 
緩存型池子通常用於執行一些生存期很短的異步型任務 
因此在一些面向連接的daemonSERVER中用得不多。 
reuse的線程,必須是timeout IDLE內的池中線程,缺省timeout60s,超過這個IDLE時長,線程實例將被終止及移出池。 
  
注意,放的入CachedThreadPool線程不必擔心其結束,超過TIMEOUT不活動,其會自動被終止。 

2.     newFixedThreadPool 

    newFixedThreadPoolcacheThreadPool差不多,也是能reuse就用,但不能隨時建新的線程 其獨特之處:任意時間點,最多隻能有固定數目的活動線程存在,此時如果有新的線程要建立,只能放在另外的隊列中等待,直到當前的線程中某個線程終止直接被移出池子。

   cacheThreadPool不同,FixedThreadPool沒有IDLE機制(可能也有,但既然文檔沒提,肯定非常長,類似依賴上層的TCP UDP IDLE機制之類的),所以FixedThreadPool多數針對一些很穩定很固定的正規併發線程,多用於服務器從方法的源代碼看,cache池和fixed 池調用的是同一個底層池,只不過參數不同: fixed池線程數固定,並且是0IDLE(無IDLE 
cache
池線程數支持0-Integer.MAX_VALUE(顯然完全沒考慮主機的資源承受能力),60IDLE  

3.       ScheduledThreadPool 
調度型線程池 
這個池子裏的線程可以按schedule依次delay執行,或週期執行 

4.       SingleThreadExecutor 
單例線程,任意時間池中只能有一個線程 
用的是和cache池和fixed池相同的底層池,但線程數目是1-1,0IDLE(無IDLE

2. 控制線程的停止

線程是"進程"中某個單一順序的控制流

週期包括:1.新建 2.就緒 3.運行 4.阻塞 5.死亡

 

我們工作中經常會碰到一些關於線程的問題,當某些動作我們已經啓動線程在運行時卻需要停止,這時我們就需要想方設法讓這個線程停止,然而目前的Thread.stop()Thread.destroy()方法都已經被設爲過時,首先我們瞭解一下爲什麼不推薦用這兩個方法:

thread.stop():因爲它本質上就是不安全的。停止線程會導致解鎖它已鎖定的所有監視程序(ThreadDeath異常傳播到棧上後即解鎖監視程序)。如果這些監視程序前面保護的任何對象處於不一致狀態,則其它線程即可能將這些對象視爲處於不一致狀態。我們將這種對象稱爲損壞的對象。當線程操作損壞的對象時,可能會產生任何意外的行爲。

Thread.destroy():從來就沒有實現。如果它已經實現,則將易於出現 Thread.suspend 方式的死鎖(實際上,它大致等價於 Thread.suspend 而沒有後續的 Thread.resume)。我們現在還沒有實現它,同時也不鼓勵使用它(防止將來實現它)。儘管它易於死鎖,但在有些情況下程序寧願冒死鎖危險也不願立即退出。

使用的解決方式

1.(通用的停止線程方式)定義一個變量,在線程run方法中根據變量來控制線程的運行於停止,該變量必須是volatile(迅變的或對該變量的訪問必須是同步的),我們之前沒有注意過這個修飾符,所以會導致一些線程出現沒有按預想停止的問題。

2. 根據前面的終止線程的通用方式,得出最終還是得讓線程執行完才真正退出,只是人工製造一個異常進行拋出致使線程跑完,所以我猜測能夠人工的製造異常來導致線程完成並結束。(沒有經過實際測驗,不過理論上是可行的)例如:在異步線程中執行一個網絡請求操作,在等待很久之後需要停止操作,則可以在請求網絡連接的動作中人工製造一個網絡異常拋出,並在run方法中捕獲,則線程終止。



Android是單線程模型,這意味着Android UI操作並不是線程安全的並且這些操作必須在UI線程中執行,所以你單純的new一個Thread並且start()是不行的,因爲這違背了Android的單線程模型。那麼如何用好多線程呢?總結一下:

     

事件處理的原則:所有可能耗時的操作都放到其他線程去處理。

  Android中的Main線程的事件處理不能太耗時,否則後續的事件無法在5秒內得到響應,就會彈出ANR對話框。那麼哪些方法會在 Main線程執行呢?

  1) Activity的生命週期方法,例如:onCreate()、onStart()、onResume()等

  2) 事件處理方法,例如onClick()、onItemClick()等

  通常Android基類中以on開頭的方法是在Main線程被回調的。

  提高應用的響應性,可以從這兩方面入手。

  一般來說,Activity的onCreate()、onStart()、onResume()方法的執行時間決定了你的應用首頁打開的時間,這裏要儘量把不必要的操作放到其他線程去處理,如果仍然很耗時,可以使用SplashScreen。使用SplashScreen最好用動態的,這樣用戶知道你的應用沒有死掉。

 

 當用戶與你的應用交互時,事件處理方法的執行快慢決定了應用的響應性是否良好,一般分爲同步和異步兩種情況:

  1) 同步,需要等待返回結果。例如用戶點擊了註冊按鈕,需要等待服務端返回結果,那麼需要有一個進度條來提示用戶你的程序正在運行沒有死掉。一般與服務端交互的都要有進度條,例如系統自帶的瀏覽器,URL跳轉時會有進度條。

  2) 異步,不需要等待返回結果。例如微博中的收藏功能,點擊完收藏按鈕後是否成功執行完成後告訴我就行了,我不想等它,這裏最好實現爲異步的。

  無論同步異步,事件處理都可能比較耗時,那麼需要放到其他線程中處理,等處理完成後,再通知界面刷新。

  這裏有一點要注意,不是所有的界面刷新行爲都需要放到Main線程處理,例如TextView的setText()方法需要在Main線程中,否則會拋出CalledFromWrongThreadException,而ProgressBar的setProgress()方法則不需要在Main線程中處理。

  當然你也可以把所有UI組件相關行爲都放到Main線程中處理,沒有問題。可以減輕你的思考負擔,但你最好了解他們之間的差別,掌握事物之間細微差別的是專家。把事件處理代碼放到其他線程中處理,如果處理的結果需要刷新界面,那麼需要線程間通訊的方法來實現在其他線程中發消息給Main線程處理。

 

  3. 如何實現線程間通訊

  在Android中有多種方法可以實現其他線程與Main線程通訊,我們這裏介紹常見的兩種。

 1) 使用AsyncTask

  AsyncTask是Android框架提供的異步處理的輔助類,它可以實現耗時操作在其他線程執行,而處理結果在Main線程執行,對於開發者而言,它屏蔽掉了多線程和後面要講的Handler的概念。你不瞭解怎麼處理線程間通訊也沒有關係,AsyncTask體貼的幫你做好了。使用他你會發現你的代碼很容易被理解,因爲他們都有一些具有特定職責的方法,尤其是AsyncTask,有預處理的方法onPreExecute,有後臺執行任務的方法doInBackground,有更新進度的方法publishProgress,有返回結果的方法onPostExecute等等,這就不像post這些方法,把所有的操作都寫在一個Runnable裏。不過封裝越好越高級的API,對初級程序員反而越不利,就是你不瞭解它的原理。當你需要面對更加複雜的情況,而高級API無法完成得很好時,你就杯具了。所以,我們也要掌握功能更強大,更自由的與Main線程通訊的方法:Handler的使用。

 

 2) 使用Handler

  這裏需要了解Android SDK提供的幾個線程間通訊的類。

  2.1 Handler

  Handler在android裏負責發送和處理消息,通過它可以實現其他線程與Main線程之間的消息通訊。

  2.2 Looper

  Looper負責管理線程的消息隊列和消息循環

  2.3 Message

  Message是線程間通訊的消息載體。兩個碼頭之間運輸貨物,Message充當集裝箱的功能,裏面可以存放任何你想要傳遞的消息。

 2.4 MessageQueue

  MessageQueue是消息隊列,先進先出,它的作用是保存有待線程處理的消息。

  它們四者之間的關係是,在其他線程中調用Handler.sendMsg()方法(參數是Message對象),將需要Main線程處理的事件添加到Main線程的MessageQueue中,Main線程通過MainLooper從消息隊列中取出Handler發過來的這個消息時,會回調Handler的handlerMessage()方法。

  除了以上兩種常用方法之外,還有幾種比較簡單的方法

 3) Activity.runOnUiThread(Runnable)

  4) View.post(Runnable)

  View.postDelayed(Runnable, long)

 5) Handler.post

  Handler.postDelayed(Runnable, long)

 

 4. 利用線程池提高性能

 

  這裏我們建議使用線程池來管理臨時的Thread對象,從而達到提高應用程序性能的目的。

  線程池是資源池在線程應用中的一個實例。瞭解線程池之前我們首先要了解一下資源池的概念。在JAVA中,創建和銷燬對象是比較消耗資源的。我們如果在應用中需要頻繁創建銷燬某個類型的對象實例,這樣會產生很多臨時對象,當失去引用的臨時對象較多時,虛擬機會進行垃圾回收(GC),CPU在進行GC時會導致應用程序的運行得不到相應,從而導致應用的響應性降低。

  資源池就是用來解決這個問題,當你需要使用對象時,從資源池來獲取,資源池負責維護對象的生命週期。

  瞭解了資源池,就很好理解線程池了,線程池就是存放對象類型都是線程的資源池。

  我增加了如何在其他線程中創建Handler的例子作爲選學,前面都掌握好了的同學可以看一下,如果你需要實現一個跟Main線程類似的消息處理機制,需要其他線程可以跟你的線程通訊,可以通過這種方法實現。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章