Android 多線程

Android中實現多線程,常見的方法有:

  • 繼承Thread類
  • 實現Runnable接口
  • ThreadPoolExecutor
  • AsyncTask
  • Handler
  • ThreadLocal
  • HandlerThread
  • IntentService

Thread

具體使用

// 步驟1:創建線程類 (繼承自Thread類)
   class MyThread extends Thread{

// 步驟2:復寫run(),內容 = 定義線程行為
    @Override
    public void run(){
    ... // 定義的線程行為
    }
}

// 步驟3:創建線程對象,即 實例化線程類
  MyThread mt=new MyThread(“線程名稱”);

// 步驟4:通過 線程對象 控制線程的狀態,如 運行、睡眠、掛起  / 停止
// 此處采用 start()開啟線程
  mt.start();

匿名類使用

// 步驟1:采用匿名類,直接 創建 線程類的實例
 new Thread("線程名稱") {
                 // 步驟2:復寫run(),內容 = 定義線程行為
                    @Override
                    public void run() {       
                  // 步驟3:通過 線程對象 控制線程的狀態,如 運行、睡眠、掛起  / 停止   
                      }.start();

Runnable

具體使用

// 步驟1:創建線程輔助類,實現Runnable接口
 class MyThread implements Runnable{
    ....
    @Override
// 步驟2:復寫run(),定義線程行為
    public void run(){

    }
}

// 步驟3:創建線程輔助對象,即 實例化 線程輔助類
  MyThread mt=new MyThread();

// 步驟4:創建線程對象,即 實例化線程類;線程類 = Thread類;
// 創建時通過Thread類的構造函數傳入線程輔助類對象
// 原因:Runnable接口并沒有任何對線程的支持,我們必須創建線程類    (Thread類)的實例,從Thread類的一個實例內部運行
  Thread td=new Thread(mt);

// 步驟5:通過 線程對象 控制線程的狀態,如 運行、睡眠、掛起  / 停止
// 當調用start()方法時,線程對象會自動回調線程輔助類對象的run(),從而實現線程操作
  td.start();

匿名類使用

// 步驟1:通過匿名類 直接 創建線程輔助對象,即 實例化 線程輔助類
Runnable mt = new Runnable() {
                // 步驟2:復寫run(),定義線程行為
                @Override
                public void run() {
                }
            };

            // 步驟3:創建線程對象,即 實例化線程類;線程類 = Thread類;
            Thread mt1 = new Thread(mt, "窗口1");
       
            // 步驟4:通過 線程對象 控制線程的狀態,如 運行、睡眠、掛起  / 停止
            mt1.start();

synchronized相關問題
1、使用注意問題:鎖對象不能為空,作用域不宜過大,避免死鎖
2、Lock和synchronized如何選擇:

ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  • corePoolSize:線程池核心線程數(平時保留的線程數)
  • maximumPoolSize:線程池最大線程數(當workQueue都放不下時,啟動新線程,最大線程數)
  • keepAliveTime:超出corePoolSize數量的線程的保留時間。
  • unit:keepAliveTime單位
  • workQueue:阻塞隊列,存放來不及執行的線程
    • ArrayBlockingQueue:構造函數一定要傳大小
    • LinkedBlockingQueue:構造函數不傳大小會默認為(Integer.MAX_VALUE ),當大量請求任務時,容易造成 內存耗盡。
    • SynchronousQueue:同步隊列,一個沒有存儲空間的阻塞隊列 ,將任務同步交付給工作線程。
    • PriorityBlockingQueue : 優先隊列
  • threadFactory:線程工廠
  • handler:飽和策略
    • AbortPolicy(默認):直接拋棄
    • CallerRunsPolicy:用調用者的線程執行任務
    • DiscardOldestPolicy:拋棄隊列中最久的任務
    • DiscardPolicy:拋棄當前任務

阿里Java開發手冊建議,不要手動創建線程(new Thread),不要使用官方推薦的線程池(FixedThreadPool, SingleThreadPool, CachedThreadPool, ScheduledThreadPool ).使用 自行創建 ThreadPoolExecutor 方式。

AsyncTask

AsyncTask 類屬于抽象類,即使用時需 實現子類

public abstract class AsyncTask<Params, Progress, Result> { 
 ... 
}

// 類中參數為3種泛型類型
// 整體作用:控制AsyncTask子類執行線程任務時各個階段的返回類型
// 具體說明:
// a. Params:開始異步任務執行時傳入的參數類型,對應excute()中傳遞的參數
// b. Progress:異步任務執行過程中,返回下載進度值的類型
// c. Result:異步任務執行完成后,返回的結果類型,與doInBackground()的返回值類型保持一致
// 注:
// a. 使用時并不是所有類型都被使用
// b. 若無被使用,可用java.lang.Void類型代替
// c. 若有不同業務,需額外再寫1個AsyncTask的子類
}

AsyncTask原理=2個線程池 + Handler


Handler機制

  • Handler的處理過程運行在創建Handler的線程里
  • 一個Looper對應一個MessageQueue
  • 一個線程對應一個Looper
  • 一個Looper可以對應多個Handler
  • 線程是默認沒有Looper的,線程需要通過Looper.prepare()、綁定Handler到Looper對象、Looper.loop()來建立消息循環
  • 主線程(UI線程),也就是ActivityThread,在被創建的時候就會初始化Looper,所以主線程中可以默認使用Handler
  • 可以通過Looper的quitSafely()或者quit()方法終結消息循環,quitSafely相比于quit方法安全之處在于清空消息之前會派發所有的非延遲消息。
  • 不確定當前線程時,更新UI時盡量調用post方法

重要的類 Handler Message MessageQueue Looper

總體流程是:Handler 發送 Message 到 MessageQueue 隊列中,Looper 循環從 MessageQueue 中獲取消息 發送給 Handler,最后Handler 的 handleMessage 處理消息。

流程
原理圖
說明

幾個注意點

  • 即 主線程的Looper對象自動生成,不需手動生成;而子線程的Looper對象則需手動通過Looper.prepare()創建
  • 在子線程若不手動創建Looper對象 則無法生成Handler對象

Handler 在 dispatchMessage時:

  • 若msg.callback屬性不為空,則代表使用了post(Runnable r)發送消息,則直接回調Runnable對象里復寫的run()
  • 若msg.callback屬性為空,則代表使用了sendMessage(Message msg)發送消息,則回調復寫的handleMessage(msg)

post 和 sendMessage 區別

  • post不需外部創建消息對象,而是內部根據傳入的Runnable對象 封裝消息對象
  • 回調的消息處理方法是:復寫Runnable對象的run()

postDelay 原理

  • 調用路徑:1.Handler.postDelayed(Runnable r, long delayMillis)
    2.Handler.sendMessageDelayed(getPostMessage(r), delayMillis)
    3.Handler.sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
    4.Handler.enqueueMessage(queue, msg, uptimeMillis)
    5.MessageQueue.enqueueMessage(msg, uptimeMillis)
  • 原理流程:
    • postDelay()一個10秒鐘的Runnable A、消息進隊,MessageQueue調用 nativePollOnce() 阻塞,Looper阻塞;
    • 緊接著post()一個Runnable B、消息進隊,判斷現在A時間還沒到、正在阻塞,把B插入消息隊列的頭部(A的前面),然后調用 nativeWake() 方法喚醒線程;
    • MessageQueue.next()方法被喚醒后,重新開始讀取消息鏈表,第一個消息B無延時,直接返回給Looper;
    • Looper處理完這個消息再次調用next()方法,MessageQueue繼續讀取消息鏈表,第二個消息A還沒到時間,計算一下剩余時間(假如還剩9秒)繼續調用nativePollOnce()阻塞;
    • 直到阻塞時間到或者下一次有Message進隊;

Handler內存泄漏
Handler 的用法:新建 內部類匿名內部類

泄漏原因:Java中,非靜態內部類 匿名內部類 都默認持有 外部類的引用。主線程Looper對象的什么周期=該應用程序的生命周期。在Handler消息隊列有未處理或正在處理的的消息時,默認持有外部類的引用銷毀,由于引用關系,GC 無法回收。

解決方案:

  • 靜態內部類+弱引用

      private static class FHandler extends Handler{
    
      // 定義 弱引用實例
      private WeakReference<Activity> reference;
    
      // 在構造方法中傳入需持有的Activity實例
      public FHandler(Activity activity) {
          // 使用WeakReference弱引用持有Activity實例
          reference = new WeakReference<Activity>(activity); 
     }
    
      // 通過復寫handlerMessage() 從而確定更新UI的操作
      @Override
      public void handleMessage(Message msg) {
          switch (msg.what) {
              case 1:
                  Log.d(TAG, "收到線程1的消息");
                  break;
              case 2:
                  Log.d(TAG, " 收到線程2的消息");
                  break;
          }
        }
     }
    
  • 當外部類結束生命周期時,清空Handler內消息隊列

      @Override
      protected void onDestroy() {
      super.onDestroy();
      mHandler.removeCallbacksAndMessages(null);
      // 外部類Activity生命周期結束時,同時清空消息隊列 & 結束Handler生命周期
      }
    

問題補充

面試常見問題

1、多個
2、有1個。通過 Looper 源碼可知,使用 ThreadLocal 內的 ThreadLocalMap 保存 key->ThreadLocal,value->Looper。部分源碼如下:

//Looper.java
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

//ThreadLoal.java
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

3、內部類持有外部類的引用 ?MessageQueue - Message - Handler - Activity

4、主線程 ActivityThread 中的 main() 函數中已經啟動了 Looper.prepareMainLooperLooper.loop
子線程要 prepare 和 loop,用完后要 quit

5、調用 Looper 的 quit() ;釋放內存 釋放線程

6、synchronized同步鎖保證安全,所以 delaymsg 時間是不準確的

7、obtain()

8、首先主線程的Looper是不能quit的,會一直存在。所有的消息都會運行在其中??ㄋ篮妥枞骶€程會導致ANR,Looper的block只是睡眠

ThreadLocal (參考)

ThreadLocal的作用是提供線程內的局部變量,這種變量在線程的生命周期內起作用,減少同一個線程內多個函數或者組件之間一些公共變量的傳遞的復雜度。

synchronizedThreadLocal 對比:
對于多線程資源共享的問題,synchronized 僅提供一份變量,讓不同的線程排隊訪問,而ThreadLocal為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。但是ThreadLocal卻并不是為了解決并發或者多線程資源共享而設計的

所以ThreadLocal既不是為了解決共享多線程的訪問問題,更不是為了解決線程同步問題,ThreadLocal的設計初衷就是為了提供線程內部的局部變量,方便在本線程內隨時隨地的讀取,并且與其他線程隔離。

ThreadLocal的應用場景:

當某些數據是以線程為作用域并且不同線程具有不同的數據副本的時候
如:屬性動畫為每個線程設置AnimationHandler、Android的Handler消息機制中通過ThreadLocal實現Looper在線程中的存取、EventBus獲取當前線程的PostingThreadState對象或者即將被分發的事件隊列或者當前線程是否正在進行事件分發的布爾值

復雜邏輯下的對象傳遞
使用參數傳遞的話:當函數調用棧更深時,設計會很糟糕,為每一個線程定義一個靜態變量監聽器,如果是多線程的話,一個線程就需要定義一個靜態變量,無法擴展,這時候使用ThreadLocal就可以解決問題。

public T get() {
    //1、首先獲取當前線程
    Thread t = Thread.currentThread();
    //2、根據當前線程獲取一個map
    ThreadLocalMap map = getMap(t);
     .....
}


 public void set(T value) {
    //1、首先獲取當前線程
    Thread t = Thread.currentThread();
    //2、根據當前線程獲取一個map
    ThreadLocalMap map = getMap(t);
    if (map != null)
    //3、map不為空,則把鍵值對保存到map中
        map.set(this, value);
    //4、如果map為空(第一次調用的時候map值為null),則去創建一個ThreadLocalMap對象并賦值給map,并把鍵值對保存到map中。
    else
        createMap(t, value);
}

get 和 set 方法里面都有一個 ThreadLocalMap。

Android早期版本,這部分的數據結構是通過Values實現的,Values中也有一個table的成員變量,table是一個Object數組,也是以類似map的方式來存儲的。偶數單元存儲的是key,key的下一個單元存儲的是對應的value,所以每存儲一個元素,需要兩個單元,所以容量一定是2的倍數。這里的key存儲的也是ThreadLocal實例的弱引用

如何保證線程安全

  • 每個線程擁有自己獨立的ThreadLocals變量(指向ThreadLocalMap對象 )
  • 每當線程 訪問 ThreadLocals變量時,訪問的都是各自線程自己的ThreadLocalMap變量(鍵 - 值)
  • ThreadLocalMap變量的鍵 key = 唯一 = 當前ThreadLocal實例

HandlerThread

HandlerThread原理

Thread類 + Handler類機制

  • 通過繼承Thread類,快速地創建1個帶有Looper對象的新工作線程
  • 通過封裝Handler類,快速創建Handler & 與其他線程進行通信
// 步驟1:創建HandlerThread實例對象
// 傳入參數 = 線程名字,作用 = 標記該線程
 HandlerThread mHandlerThread = new HandlerThread("handlerThread");

// 步驟2:啟動線程
   mHandlerThread.start();

// 步驟3:創建工作線程Handler & 復寫handleMessage()
// 作用:關聯HandlerThread的Looper對象、實現消息處理操作 & 與其他線程進行通信
// 注:消息處理操作(HandlerMessage())的執行線程 =         mHandlerThread所創建的工作線程中執行
  Handler workHandler = new Handler( handlerThread.getLooper() ) {
        @Override
        public boolean handleMessage(Message msg) {
            ...//消息處理
            return true;
        }
    });

// 步驟4:使用工作線程Handler向工作線程的消息隊列發送消息
// 在工作線程中,當消息循環時取出對應消息 & 在工作線程執行相關操作
  // a. 定義要發送的消息
  Message msg = Message.obtain();
  msg.what = 2; //消息的標識
  msg.obj = "B"; // 消息的存放
  // b. 通過Handler發送消息到其綁定的消息隊列
  workHandler.sendMessage(msg);

// 步驟5:結束線程,即停止線程的消息循環
  mHandlerThread.quit();

IntentService

開啟一個新的工作線程

@Override
public void onCreate() {
super.onCreate();

// 1. 通過實例化andlerThread新建線程 & 啟動;故 使用IntentService時,不需額外新建線程
// HandlerThread繼承自Thread,內部封裝了 Looper
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();

// 2. 獲得工作線程的 Looper & 維護自己的工作隊列
mServiceLooper = thread.getLooper();

// 3. 新建mServiceHandler & 綁定上述獲得Looper
// 新建的Handler 屬于工作線程 ->>分析1
mServiceHandler = new ServiceHandler(mServiceLooper); 
}


   /** 
 * 分析1:ServiceHandler源碼分析
 **/ 
 private final class ServiceHandler extends Handler {

     // 構造函數
     public ServiceHandler(Looper looper) {
     super(looper);
   }

    // IntentService的handleMessage()把接收的消息交給onHandleIntent()處理
    @Override
     public void handleMessage(Message msg) {

      // onHandleIntent 方法在工作線程中執行
      // onHandleIntent() = 抽象方法,使用時需重寫 ->>分析2
      onHandleIntent((Intent)msg.obj);
      // 執行完調用 stopSelf() 結束服務
      stopSelf(msg.arg1);

    }
}

   /** 
 * 分析2: onHandleIntent()源碼分析
 * onHandleIntent() = 抽象方法,使用時需重寫
 **/ 
  @WorkerThread
  protected abstract void onHandleIntent(Intent intent);

通過onStartCommand() 將Intent 傳遞給服務 & 依次插入到工作隊列中

/** 
  * onStartCommand()源碼分析
  * onHandleIntent() = 抽象方法,使用時需重寫
  **/ 
  public int onStartCommand(Intent intent, int flags, int startId) {

// 調用onStart()->>分析1
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

/** 
  * 分析1:onStart(intent, startId)
  **/ 
  public void onStart(Intent intent, int startId) {

// 1. 獲得ServiceHandler消息的引用
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;

// 2. 把 Intent參數 包裝到 message 的 obj 發送消息中,
//這里的Intent  = 啟動服務時startService(Intent) 里傳入的 Intent
msg.obj = intent;

// 3. 發送消息,即 添加到消息隊列里
mServiceHandler.sendMessage(msg);
  }

從上面源碼可看出:IntentService本質 = Handler + HandlerThread:

  • 通過HandlerThread 單獨開啟1個工作線程:IntentService
  • 創建1個內部 Handler :ServiceHandler
  • 綁定 ServiceHandler 與 IntentService
  • 通過 onStartCommand() 傳遞服務intent 到ServiceHandler 、依次插入Intent到工作隊列中 & 逐個發送給 onHandleIntent()
  • 通過onHandleIntent() 依次處理所有Intent對象所對應的任務

不建議通過 bindService() 啟動 IntentService
原因:bind的生命周期為

onCreate() ->> onBind() ->> onunbind()->> onDestory()

并沒有執行 onStart 或 onStartCommand ,故不會將消息發送到消息隊列,那么onHandleIntent()將不會回調,即無法實現多線程的操作


多線程并發問題

  • 當只有一個線程寫,其它線程都是讀的時候,可以用volatile修飾變量
  • 當多個線程寫,那么一般情況下并發不嚴重的話可以用Synchronized,不過Synchronized有局限性,比如不能設置鎖超時,不能通過代碼釋放鎖。
  • ReentranLock 可以通過代碼釋放鎖,可以設置鎖超時。
  • 高并發下,Synchronized、ReentranLock 效率低,因為同一時刻只有一個線程能進入同步代碼塊,如果同時有很多線程訪問,那么其它線程就都在等待鎖。這個時候可以使用并發包下的數據結構,例如ConcurrentHashMap,LinkBlockingQueue,以及原子性的數據結構如:AtomicInteger。

詳見 說說多線程并發

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容