執行緒和執行緒池

小編發表於2019-03-04

執行緒

在java中實現執行緒的方式:

  1. 繼承Thread類
  2. 實現Runable介面。

main方法其實也是一個執行緒。
在java中,每次程式執行至少啟動2個執行緒。一個是main執行緒,一個是垃圾收集執行緒。

  • 對比
    實現Runnable介面比繼承Thread類所具有的優勢:
    1):適合多個相同的程式程式碼的執行緒去處理同一個資源
    2):可以避免java中的單繼承的限制
    3):增加程式的健壯性,程式碼可以被多個執行緒共享,程式碼和資料獨立
  • yield()方法

    Thread.yield()方法作用是:暫停當前正在執行的執行緒物件,並執行其他執行緒。
    yield()應該做的是讓當前執行執行緒回到可執行狀態,以允許具有相同優先順序的其他執行緒獲得執行機會。因此,使用yield()的目的是讓相同優先順序的執行緒之間能適當的輪轉執行。但是,實際中無法保證yield()達到讓步目的,因為讓步的執行緒還有可能被執行緒排程程式再次選中。

    結論:yield()從未導致執行緒轉到等待/睡眠/阻塞狀態。在大多數情況下,yield()將導致執行緒從執行狀態轉到可執行狀態,但有可能沒有效果。

  • join()方法
    保證當前執行緒停止執行,直到該執行緒所加入的執行緒完成為止,當前執行緒方可繼續執行。然而,如果它加入的執行緒沒有存活,則當前執行緒不需要停止。

AsyncTask

  • 內部由兩個執行緒池和一個Handler組成。
    1. SerialExecutor:用於任務的排隊,一次執行一個。
    2. ThreadPoolExecutor:用於真正的執行任務。
    3. InternalHandler:用於傳送結果資料從子執行緒到主執行緒。
  • 執行流程:
    構造方法中例項化WorkerRunnable和FutureTask物件。
    WorkerRunnable將Params引數封裝,並將自己封裝在FutureTask中,一旦FutureTask執行run方法時,會去呼叫WorkRunnable的call方法並返回Result值。call方法中就執行了AsyncTask的doInBackground方法。並呼叫postResult方法將結果傳送出去。

    mWorker = new WorkerRunnable<Params, Result>() {
      public Result call() throws Exception {
          mTaskInvoked.set(true);
    
          Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
          //noinspection unchecked
          Result result = doInBackground(mParams);
          Binder.flushPendingCommands();
          return postResult(result);
      }
    };
    
    private Result postResult(Result result) {
          @SuppressWarnings("unchecked")
          Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                  new AsyncTaskResult<Result>(this, result));
          message.sendToTarget();
          return result;
    }複製程式碼

    那麼FutureTask是什麼時候執行的?
    FutureTask充當了Runnable的作用,交給SerialExecutor的execute方法執行。FutureTask是一個併發類,可以中途取消的用於非同步計算的類。

    SerialExecutor的execute方法首先把FutureTask插入到mTasks任務佇列中,如果沒有活動的任務,則執行下一個。當一個任務執行完成,會繼續呼叫scheduleNext方法執行下一個,直到所有任務都被執行。

    THREAD_POOL_EXECUTOR.execute(mActive);才是真正執行任務的方法。使用的是ThreadPoolExecutor執行緒池。

    private static class SerialExecutor implements Executor {
      final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
      Runnable mActive;
    
      public synchronized void execute(final Runnable r) {
          //將FutureTask插入到mTasks任務佇列中
          mTasks.offer(new Runnable() {
              public void run() {
                  try {
                      // 執行FutureTask的run方法,進而執行call方法
                      r.run();
                  } finally {
                      // 序列執行下一個任務
                      scheduleNext();
                  }
              }
          });
          //沒有正在活動的任務,執行下一個AsyncTask。
          if (mActive == null) {
              scheduleNext();
          }
      }
    
      protected synchronized void scheduleNext() {
          if ((mActive = mTasks.poll()) != null) {
              THREAD_POOL_EXECUTOR.execute(mActive);
          }
      }
    }複製程式碼
  • 注意:

    1. AsyncTask物件必須在主執行緒中建立
    2. execute必須在主執行緒中呼叫
    3. 一個AsyncTask只能呼叫一次execute方法,

HandlerThread

繼承了Thread類,本質還是執行緒。但是可以直接使用Handler的Thread。在run方法中通過Looper.prepare建立訊息佇列,並開啟訊息迴圈。使得可以再次執行緒中建立Handler。

同時,它還解決了一個Looper與Handler的同步問題。可以保證根據當前執行緒的Looper建立Handler時,Looper物件的獲取不為空。
參考《深入理解Android 卷I》159頁

    /**
     * This method returns the Looper associated with this thread. If this thread not been started
     * or for any reason is isAlive() returns false, this method will return null. If this thread 
     * has been started, this method will block until the looper has been initialized.  
     * @return The looper.
     */
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }

        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }複製程式碼

由於loop開啟了無限迴圈,因此可以通過quit或者quitSafely方法終止執行緒執行。

典型應用場景就是在IntentService中。

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }複製程式碼

IntentService

一個可以處理非同步請求的Service.服務開啟後,工作執行緒會依次處理每個Intent,任務執行完畢後會自動關閉。

相對於執行緒而言,IntentService更適合執行一些高優先順序的後臺任務。

    @Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }複製程式碼

執行緒池

  • 優點
    1. 重用執行緒池中的執行緒,避免因為執行緒的建立和銷燬帶來的效能開銷。
    2. 有效控制執行緒的最大併發數,避免因大量的執行緒之間互相搶佔系統資源而導致的阻塞 。
    3. 能夠對執行緒進行簡單的管理,並提供定時執行和執行迴圈間隔執行等功能
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {  }複製程式碼
  • 變數

    • corePoolSize: 核心執行緒數
    • maximumPoolSize: 最大執行緒數
    • workQueue:任務佇列,提交的Runnable物件儲存在這裡。
    • keepAliveTime: 非核心執行緒存活時間
    • unit:keepAliveTime的時間單位。
    • threadFactory:為執行緒池提供新執行緒的工廠。
    • handler:異常處理策略。
  • 規則

    1. 如果此時執行緒池中的數量小於corePoolSize,即使執行緒池中的執行緒都處於空閒狀態,也要建立新的執行緒來處理被新增的任務。
    2. 如果此時執行緒池中的數量等於 corePoolSize,但是緩衝佇列 workQueue未滿,那麼任務被放入緩衝佇列。
    3. 如果此時執行緒池中的數量大於corePoolSize,緩衝佇列workQueue滿,並且執行緒池中的數量小於maximumPoolSize,建新的執行緒來處理被新增的任務。
    4. 如果此時執行緒池中的數量大於corePoolSize,緩衝佇列workQueue滿,並且執行緒池中的數量等於maximumPoolSize,那麼通過 handler所指定的策略來處理此任務。

相關文章