1. 程式人生 > >Android 四種執行緒池

Android 四種執行緒池

為什麼要使用執行緒池

  1. 當同時併發多個網路執行緒時,引入執行緒池技術會極大地提高APP的效能。
  2. 顯著減少了建立執行緒的數目。
  3. 防止記憶體過度消耗。控制活動執行緒的數量,防止併發執行緒過多。
    使用條件:假設在一臺APP完成一項任務的時間為T
    • T1 建立執行緒的時間
    • T2 線上程中執行任務的時間,包括執行緒間同步所需時間
    • T3 執行緒銷燬的時間

顯然T = T1+T2+T3。注意這是一個極度簡化的假設。可以看出T1,T3是多執行緒本身的帶來的開銷,我們渴望減少T1,T3所用的時間,從而減少T的時間。但一些執行緒的使用者並沒有注意到這一點,所以在程式中頻繁的建立或銷燬執行緒,這導致T1和T3在T中佔有相當比例。顯然這是突出了執行緒的弱點(T1,T3),而不是優點(併發性)。

執行緒池技術正是關注如何縮短或調整T1,T3時間的技術,從而提高APP程式效能的。它把T1,T3分別安排在伺服器程式的啟動和結束的時間段或者一些空閒的時間段,這樣在伺服器程式處理客戶請求時,不會有T1,T3的開銷了。

四種執行緒池各自的特點

  1. newCachedThreadPool()
    快取型池子,先檢視池中有沒有以前建立的執行緒,如果有,就reuse.如果沒有,就建一個新的執行緒加入池中。能reuse的執行緒,必須是timeout IDLE內的池中執行緒,預設timeout是60s,超過這個IDLE時長,執行緒例項將被終止及移出池。快取型池子通常用於執行一些生存期很短的非同步型任務 。
  2. newFixedThreadPool()
    fixedThreadPool與cacheThreadPool差不多,也是能reuse就用,但不能隨時建新的執行緒 其獨特之處:任意時間點,最多隻能有固定數目的活動執行緒存在,此時如果有新的執行緒要建立,只能放在另外的佇列中等待,直到當前的執行緒中某個執行緒終止直接被移出池子。和cacheThreadPool不同:fixedThreadPool池執行緒數固定,但是0秒IDLE(無IDLE)。這也就意味著建立的執行緒會一直存在。所以fixedThreadPool多數針對一些很穩定很固定的正規併發執行緒,多用於伺服器。
  3. newScheduledThreadPool()
    排程型執行緒池。這個池子裡的執行緒可以按schedule依次delay執行,或週期執行 。0秒IDLE(無IDLE)。
  4. SingleThreadExecutor
    單例執行緒,任意時間池中只能有一個執行緒 。用的是和cache池和fixed池相同的底層池,但執行緒數目是1-1,0秒IDLE(無IDLE)。

從一個Demo開始

package com.example.executortest;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class MainActivity extends Activity implements View.OnClickListener {

    private static final String TAG = "Executor";

    /** 總共多少任務(根據CPU個數決定建立活動執行緒的個數,這樣取的好處就是可以讓手機承受得住) */
    // private static final int count = Runtime.getRuntime().availableProcessors() * 3 + 2;

    /** 總共多少任務 */
    private static final int count = 3;

    /** 所有任務都一次性開始的執行緒池  */
    private static ExecutorService mCacheThreadExecutor = null;

    /** 每次執行限定個數個任務的執行緒池 */
    private static ExecutorService mFixedThreadExecutor = null;

    /** 建立一個可在指定時間裡執行任務的執行緒池,亦可重複執行 */
    private static ScheduledExecutorService mScheduledThreadExecutor = null;

    /** 每次只執行一個任務的執行緒池 */
    private static ExecutorService mSingleThreadExecutor = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initExecutorService();
        Log.i(TAG, "onCreate");

    }

    private void initExecutorService() {
        mCacheThreadExecutor = Executors.newCachedThreadPool();// 一個沒有限制最大執行緒數的執行緒池
        mFixedThreadExecutor = Executors.newFixedThreadPool(count);// 限制執行緒池大小為count的執行緒池
        mScheduledThreadExecutor = Executors.newScheduledThreadPool(count);// 一個可以按指定時間可週期性的執行的執行緒池
        mSingleThreadExecutor = Executors.newSingleThreadExecutor();// 每次只執行一個執行緒任務的執行緒池
    }

    private void initView() {
        findViewById(R.id.mCacheThreadExecutorBtn).setOnClickListener(this);
        findViewById(R.id.mFixedThreadExecutorBtn).setOnClickListener(this);
        findViewById(R.id.mScheduledThreadExecutorBtn).setOnClickListener(this);
        findViewById(R.id.mSingleThreadExecutorBtn).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.mCacheThreadExecutorBtn:
                ExecutorServiceThread(mCacheThreadExecutor);
                break;
            case R.id.mFixedThreadExecutorBtn:
                ExecutorServiceThread(mFixedThreadExecutor);
                break;
            case R.id.mScheduledThreadExecutorBtn:
                ExecutorScheduleThread(mScheduledThreadExecutor);
                break;
            case R.id.mSingleThreadExecutorBtn:
                ExecutorServiceThread(mSingleThreadExecutor);
                break;
        }
    }

    private void ExecutorServiceThread(ExecutorService executorService) {
        for (int i = 0; i < 9; ++i) {
            final int index = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2 * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    Log.i(TAG, "Thread:" + Thread.currentThread().getId() + " activeCount:" + Thread.activeCount() + " index:" + index);
                }
            });
        }
    }

    private void ExecutorScheduleThread(ScheduledExecutorService scheduledExecutorService) {
        for (int i = 0; i < 9; ++i) {
            final int index = i;
            scheduledExecutorService.schedule(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2 * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    Log.i(TAG, "Thread:" + Thread.currentThread().getId() + " activeCount:" + Thread.activeCount() + " index:" + index);
                }
            },2, TimeUnit.SECONDS);
        }
    }
}

佈局四個Button就不貼了,分別對應newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor。

60s之內點選兩次mCacheThreadExecutorBtn,60s後再次點選

mCacheThreadExecutor
從程式碼中我們可以知道,每次點選輸出9條Log。由強迫症的同學可能會問為什麼是9條而不是整數10,我會告訴你:因為我願意!

一共三次點選,一大段空格和游標為分界線。仔細對比可以發現:第一次點選開啟了9個執行緒。沒有複用任何執行緒,60s內第二次點選,全部複用了第一次點選開啟的執行緒。60s後第三次點選,由於IDLE機制,原來開啟的執行緒被自動終止,重新開啟了9個新執行緒。

點選mFixedThreadExecutorBtn,60s後再次點選

mFixedThreadExecutor
兩次點選,游標為分割線。程式碼中可以看到:mFixedThreadExecutor = Executors.newFixedThreadPool(3);設定執行緒池的最大數量為3。看到這裡有強迫症的小夥伴是不是突然覺得上文9條Log的9是不是爽了一些。畢竟是3的整數倍。從輸出的log可以看出第一次建立了125、126、127三個執行緒。而在這之後,無論多長時間再次點選mFixedThreadExecutorBtn,都在複用已經建立了的3個執行緒。0秒IDLE(無IDLE)。

點選mScheduledThreadExecutorBtn,60s後再次點選

mScheduledThreadExecutor
兩次點選,游標為分割…割…割線。這個和mFixedThreadExecutor一樣,區別是首次建立Thread有個啟動延遲時間,本Demo是2s。0秒IDLE(無IDLE)

點選mSingleThreadExecutorBtn,60s後再次點選

mSingleThreadExecutor
兩次點選,游標為分割線。Log顯示這種執行緒池從建立122執行緒之後就一直複用這個執行緒。心疼newSingleThreadExecutor執行緒池,身為一個“池”,居然只能裝下一個執行緒。唯一的用處是保證所有任務按照FIFO(First In First Out)順序執行。