Android多線程應用

Android中提供瞭如下幾種實現多線程的方法: java

1. Thread+Handler實現多線程:
不要在本身新建的Thread裏對UI進行操做。可使用Handler與主線程進行交互。有如下兩種使用方式:
<1>. 發送消息: 數據庫

// Handler定義在哪一個線程,就被綁定在該線程
Handler handler = new Handler() {
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        // 在handler所在的線程上處理消息,若是handler所在的線程是主線程,能夠更新UI
    }
};
// 新建線程Thread
new Thread(new Runnable(){   
    public void run() {
        while(!Thread.currentThread().isInterrupted()) { 
            Message msg = handler.obtainMessage();  
            msg.what = XX;  
            msg.arg1 = XX;  
            msg.obj = XX;
            msg.sendToTarget();
            // 在新線程中分發Message對象到handler所在的線程
        } 
    }      
}).start();

<2>. 發送Runnable對象: 緩存

// Handler定義在哪一個線程,就被綁定在該線程
Handler handler = new Handler();
// 新建線程Thread
new Thread(){  
    public void run(){     
      while(!Thread.currentThread().isInterrupted()) {                 
            handler.post(new Runnable(){
                // 在新線程中分發Runnable對象到handler所在的線程中
              public void run(){
                // 運行在handler所在的線程,若是handler所在的線程是主線程,能夠更新UI
                }
            });
        } 
    }                     
}.start();// 新建了線程

以上例子都是在主線程中建立Handler,在子線程中引用。若是要在本身的Thread中建立Handler來與主線程交互的話,必須在new Handler前加上Looper.prepare(),在建立後加上Looper.loop()。緣由:只有主線程默認帶了消息隊列,自定義線程是不帶MessageQueue和Looper的。 安全

<3>.Thread中斷問題: 多線程

雖然有stop()接口,可是不安全,會發生異常。因此,經過調用interrupt()設置中斷標示,在Thread的run()函數中,若是有循環語句,在循環中經過斷定isInterrupted(),若是爲true,主動退出run()函數便可結束線程,達到中斷的目的。 併發

<4>. 一組線程的執行是無序的,能夠實現協同工做,也能夠各自獨立工做。 框架

// 多個線程共享同一個TaskRun的實例,即實現協做工做。
  TaskRun task = new TaskRun(); //TaskRun實現Runnable接口的run()函數 異步

  new Thread(task,"t1").start();
  new Thread(task,"t2").start();
  
  // 多個線程各自初始化各自的實例,即各自作各自的工做。
  MyThread myT1 = new MyThread(); //MyThread繼承了Thread,本身實現run()函數
  MyThread myT2 = new MyThread();
  myT1.start();
  myT2.start(); ide

 

2. AsyncTask 異步任務: 函數

一個實例僅執行一次,屢次execute會拋出異常。因此,要執行幾個任務就new幾個實例。內部會建立一個進程做用域的線程池來管理要運行的任務。

2.3平臺之前調用execute,全部的任務併發執行,內部的線程池限制是5個,後來默認串行執行,只能調用executeOnExecutor((ExecutorService)Executors.newCachedThreadPool())實現併發,這裏沒有使用默認的線程池,而使用自定義的。

適用於短期的多線程任務(幾分鐘)。若是要完成長時間的運行任務,最好使用Executor,ThreadPoolExecutor,FutureTask。
<1>.AsyncTask<Params, Progress, Result>
是抽象類,定義了三種泛型類型,分別表明「啓動任務執行的輸入參數」(如HTTP請求的URL)、「後臺任務執行的進度」、「後臺計算結果的類型」。若是沒有被使用,能夠用Void代替。

<2>.AsyncTask定義的方法:
execute(Params... params),執行一個異步任務,須要咱們在代碼中調用此方法,觸發異步任務的執行。
onPreExecute(),在execute(Params... params)被調用後當即執行,通常用來在執行後臺任務前對UI作一些標記。
doInBackground(Params... params),在onPreExecute()完成後當即執行,用於執行較爲費時的操做,此方法將接收輸入參數和返回計算結果。不能更新UI,只能在執行過程當中調用publishProgress(Progress... values)來更新進度信息。
onProgressUpdate(Progress... values),在調用publishProgress(Progress... values)時被執行,將進度信息更新到UI組件上。
onPostExecute(Result result),當後臺操做結束時,此方法將會被調用,計算結果將作爲參數傳遞到此方法中,直接將結果顯示到UI組件上。

<3>.終止線程:

cancel()終止後臺線程的運行,能夠先用isCanceled()來詢問,避免重複叫停。
執行cancel(),將會回調onCanceled(),同時onPostExecute()就不會被回調。若是想作到及時中斷,在doInBackground()中,檢測該狀態位來判斷是否繼續運行。

在 Java 中,非靜態匿名內部類會持有其外部類的隱式引用,該引用會致使 Activity 被保留,而不是被垃圾回收機制回收。將線程類(Thread,AsyncTask)聲明爲私有的靜態內部類避免了 Activity context 的內存泄漏問題,但在配置發生改變後,線程仍然會執行。緣由在於,DVM 虛擬機持有全部運行線程的引用,不管這些線程是否被回收,都與 Activity 的生命週期無關。運行中的線程只會繼續運行,直到Android 系統將整個應用進程殺死。在退出當前Activity 前使用 onDestroy() 方法結束運行中線程是個不錯的選擇。

private MyTask task;
private static int i = 1;

private static class MyTask extends AsyncTask<Void, Void, Integer>{
		@Override
		protected Integer doInBackground(Void... params) {
			while(!isCancelled()) {
				i++;
				Log.d("i = ", String.valueOf(i));
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			return null;
		}
	}

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
	task = new MyTask();
	task.execute();
}
	
@Override
protected void onDestroy() {
	super.onDestroy();
	if(task!=null && task.getStatus()==Status.RUNNING) {
		task.cancel(true);
	}
}


3. 線程池:
Java經過Executors提供的四種線程池,分別爲:
<1>. newCachedThreadPool建立一個可緩存線程池,調用 execute 將重用之前構造的線程(若是線程可用),若無可回收,則新建線程。
有IDLE機制,線程若是超過TIMEOUT(60s)不活動,其會自動被終止。一般用於執行一些生存期很短的異步型任務。

<2>. newFixedThreadPool 建立一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
沒有IDLE機制,在某個線程被顯式地關閉以前,池中的線程將一直存在。多數針對一些很穩定很固定的正規併發線程。
定長線程池的大小最好根據系統資源進行設置。如Runtime.getRuntime().availableProcessors()

<3>. newScheduledThreadPool 建立一個定長線程池,支持定時及週期性任務執行。
如下是3種定時器的寫法與比較:

Handler.postDelayed其實是運行在主線程的,也能夠新建一個Thread來發送Runnable。

在Timer機制中,只封裝了一個線程來執行定時任務,並且有時有異常發生。

ScheduledExecutorService比Timer更安全,功能更強大,是最優化方案。

private static final int REGUEST_MESSAGE_TIME = 1000*60*60*2;//2小時
 
//使用Handler做爲定時器,實際上Runnable仍是在主線程中運行
private Handler mTimerHandler = new Handler();
private Runnable mTimerRunnable = new Runnable() {
    @Override  
    public void run() {
        // 2小時後執行一次Runnable
        msgTimerHandler.postDelayed(this, REGUEST_MESSAGE_TIME);
    }  
};
 
// Timer + TimerTask定時器,在Timer機制中,只有一個線程來執行定時任務
private Timer mTimer = null;
private TimerTask mTimerTask = new TimerTask() { 
    @Override
    public void run() {
    }
};
 
// 使用線程池的方式支持定時線程任務
private ScheduledExecutorService mScheduledExecutorService = null;
 
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 立刻執行Runnable
    msgTimerHandler.post(mTimerRunnable);
 
    mTimer = new Timer();
    // 每隔2小時執行一次TimerTask,循環任務
    mTimer.schedule(mTimerTask, 0, REGUEST_MESSAGE_TIME);
 
    // 該線程池能夠容納5個線程
    mScheduledExecutorService= Executors.newScheduledThreadPool(5);
    // 立刻執行Runnable ,而後每隔2小時執行一次Runnable
    mScheduledExecutorService.scheduleAtFixedRate(new Runnable() {  
        @Override  
        public void run() {   
        }  
    }, 0, 2, TimeUnit.HOURS); 
}
protected void onDestroy() {
    super.onDestroy();
    // 清除以該Handler爲target的全部Message和Callback,防止內存泄露
    msgTimerHandler.removeCallbacksAndMessages(null);
 
    if ( mTimer != null ) {
        //不但能夠結束當前schedule,連整個Timer的線程都會結束掉
        mTimer.cancel();
        mTimer = null;
    }
 
    //關閉線程池隊列中的全部等待任務
    mScheduledExecutorService.shutdown();
}


<4>. newSingleThreadExecutor 建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務,保證全部任務按照指定順序(FIFO, LIFO, 優先級)執行。 不用考慮同步的問題。
沒有IDLE機制。可用於數據庫操做,文件操做,應用批量安裝,應用批量刪除等不適合併發但可能IO阻塞性及影響UI線程響應的操做。

好處:
a. 重用存在的線程,減小對象建立、消亡的開銷,性能佳。
b. 可有效控制最大併發線程數,提升系統資源的使用率,同時避免過多資源競爭,避免堵塞。
c. 提供定時執行、按期執行、單線程、併發數控制等功能。

開啓線程的兩種方法:
Future<?> submit(Runnable task) /*用Future來判斷線程運行狀態*/
void execute(Runnable command)     /*沒法判斷線程是否成功完成*/

關閉線程池的方法:

shutdown()  中止接受新任務且等待已經提交的任務執行完成(已經提交的任務會分兩類:一類是已經在執行的,另外一類是尚未開始執行的),當全部已經提交的任務執行完畢後將會關閉ExecutorService。
shutdownNow()  試圖中止全部正在執行的活動任務(調用對應線程的interrupt(),設置中斷標誌位而已),暫停處理正在等待的任務,並返回等待執行的任務列表。

中斷線程池某個任務:
向ExecutorService提交任務調用submit方法以後,返回值是個Future對象,可使用這個對象的cancel方法來取消對應任務。
若是任務還在等待隊列中,會直接取消掉。
若是任務已經執行了,cancel的boolean值參數mayInterruptIfRunning用於表示當任務已經開始執行時,是否須要嘗試中斷執行該任務的線程。(注意:這只是表示任務是否可以接收中斷,而不是表示任務是否能檢測並處理中斷)
因此仍是得在本身的任務中經過斷定中斷標誌位來結束任務。

建議:
與主線程有交互時用AsyncTask,不然就用Thread。
當有須要大量線程執行任務時,必定要建立線程池,好比批量下載圖片或文件。
對於想要當即開始執行的異步任務,要麼直接使用Thread,要麼單首創建線程池提供給AsyncTask,默認的AsyncTask不必定會當即執行你的任務。


4. 訪問臨界資源時,使用線程同步機制:
synchronized關鍵字能夠修飾方法,也能夠修飾代碼塊,但不能修飾構造器,屬性等。

 

5. 處理線程阻塞中斷:

在執行涉及線程調度的阻塞調用時(例如wait、sleep和join),若是發生中斷,被阻塞線程會「儘量快的」拋出InterruptedException。所以,咱們就能夠用下面的代碼框架來處理:
    try {
        //wait、sleep或join
       }
    catch(InterruptedException e) {
        //某些中斷處理工做
      }

<1>.sleep()靜態方法
在指定時間內讓當前正在執行的線程暫停執行,但不會釋放「鎖標誌」。
使當前線程進入阻塞狀態,在指定時間內不會執行。其餘線程在此期間能夠得到運行機會。線程睡眠到期自動甦醒,並返回到可運行狀態,不是運行狀態。什麼時候運行還要看線程池如何調度。

<2>.wait()方法
在其餘線程調用對象的notify或notifyAll方法前,致使當前線程等待。線程會釋放掉它所佔有的「鎖標誌」,從而使別的線程有機會搶佔該鎖。
當前線程必須擁有當前對象鎖。若是當前線程不是此鎖的擁有者,會拋出IllegalMonitorStateException異常。
喚醒當前對象鎖的等待線程使用notify或notifyAll方法,也必須擁有相同的對象鎖,不然也會拋出IllegalMonitorStateException異常。
wait()和notify()必須在synchronized函數或synchronized block中進行調用。若是在non-synchronized函數或non-synchronized block中進行調用,雖然能編譯經過,但在運行時會發生IllegalMonitorStateException的異常。

<3>.yield靜態方法
暫停當前正在執行的線程對象,並執行其餘線程。
yield()只是使當前線程從新回到可執行狀態,因此執行yield()的線程有可能在進入到可執行狀態後立刻又被執行。
yield()只能使同優先級或更高優先級的線程有執行的機會。

<4>.join方法
保證當前線程中止執行,直到該線程所加入的線程完成爲止。然而,若是它加入的線程沒有存活,則當前線程不須要中止。

如在t1線程中調用t2.join(),則須要t2線程執行完後t1方能繼續執行。

相關文章
相關標籤/搜索