Android的線程與線程池知識

I. 線程

線程在Android系統中扮演者一個很重要的角色,從用途上來說,可以分爲主線程和子線程,主線程一般用來處理界面與用戶的交互,而子線程則往往用來執行一些耗時操作,例如I/O操作和網絡訪問,在Android3.0之後網絡訪問必須放到子線程中執行,否則會拋異常(NetworkOnMainThreadException),這樣做的目的也是爲了防止用戶在主線程中做耗時操作,這樣很容易引起ANR。在Android系統中還有一些扮演線程的角色:AsyncTask、IntentService和HandlerThread,雖然它們都有別於傳統的線程,但是它們的本質仍然是傳統的線程。

II. 線程池

線程是操作系統中最小的調度單位,操作系統創建、銷燬線程的開銷代價是比較大的,試想一下如果不斷地創建銷燬線程,那麼系統就會頻繁地創建銷燬線程,同時創建太多的線程在CUP調度時切換調度單位也是一個問題,除非線程數量少於CUP的核數才能保證絕對的並行。頻繁地創建銷燬線程帶來不小的系統開銷,這個時候線程池就是明智的選擇,一個線程池會緩存一定數量的線程,通過線程池來避免頻繁的創建銷燬線程所帶來的系統開銷。Android中的線程池來源於Java,主要通過executor來派生特定類型的線程池,不同種類的線程池有不同的特性。

III. AsyncTask

AsyncTask是一個抽象泛型類,public abstract class AsyncTask<Params, Progress, Result>,這三個參數中的Params是執行異步任務傳入的參數類型,Progress是異步任務需要向外發出的進度值得類型,即publishProgress方法的參數類型,Result是doInbackground方法的返回值類型。下面是一個例子:

new MyAsyncTask("task1").execute("param1");
new MyAsyncTask("task1").execute("param1", "param2");
new MyAsyncTask("task1").execute("param1", "param2", "param3");
class MyAsyncTask extends AsyncTask<String, Integer, String>{

    private String name;

    public MyAsyncTask(String tag){
        name = tag;
    }

    /**
     * 在主線程工作,一般在這個方法中提示正在加載某些資源
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }
    /**
     * 在子線程工作(使用線程池),在後臺異步加載資源,可以通過publishProgress(Progress...         values)調用onProgressUpdate來更新進度
     *
     * @param params 這個參數是new MyAsyncTask().execute(params) 時傳遞的參數,其中 ... 表示參數是動態的,
     *               接收的時候是一個數組
     *
     * @return 返回值會傳遞到onPostExecute方法的參數
     */
    @Override
    protected String doInBackground(String... params) {
        //模擬耗時任務
        SystemClock.sleep(2000);
        Log.d(TAG, "doInBackground: " + name + ":params.length=" + params.length);
        int progress = 10;
        publishProgress(progress);
        return name;
    }

    /**
     * 在主線程工作,一般在這個方法根據參數去更新UI,例如消除掉正在加載的對話框,將結果刷新到界面
     *
     * @param s doInBackground的返回值
     *
     */
    @Override
    protected void onPostExecute(String s) {
        super.onPostExecute(s);
    }

    /**
     * 在主線程工作,用於更新進度,比如在下載的場景提示當前的下載進度
     *
     * @param values 在doInBackground中調用publishProgress(Progress... values)的參數
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        //Log.d(TAG, "onProgressUpdate: progress=" + values[0]);
    }
}

此處輸入圖片的描述

從打印結果可以看出任務時串行執行的,打印結果相差了2秒,我使用的模擬器版本是Android-22(5.1.1)。Android1.6之前的版本是串行的,1.6到Android3.0之間是並行的,3.0開始默認又變爲串行的了,當然在3.0之後我們可以通過AsyncTask的 executeOnExecutor來實現並行。

new MyAsyncTask("task1 on executor").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
new MyAsyncTask("task2 on executor").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
new MyAsyncTask("task3 on executor").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");

此處輸入圖片的描述

我們在代碼中模擬通過SystemClock.sleep(2000)來模擬還是任務, 但是通過結果可以看到是在同一秒鐘(46)打印的結果,這就意味着任務是並行處理的。

IV. HandlerThread

HandlerThread繼承自Thread,其實也沒做什麼,最主要的工作是幫我們做了 Looper.prepare() 和Looper.loop(),然後HanlderThread可以直接使用Handler。下面是HandlerThread的run方法的實現:

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

你可能會發現其中有一句代碼notifyAll();,這是幹什麼的,先看看getLoopere方法

/**
 * 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;
}

你會發現裏面有一個wait(),其實就是通知等待的代碼,你可以執行了,這是一個同步機制。

HandlerThread用法的一個例子:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private Handler mHandler;
    private HandlerThread mThread;

     @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mThread = new HandlerThread("#HandlerThread");
        mThread.start();
        mHandler = new Handler(mThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.d(TAG, "handleMessage: " + msg.what);
                mHandler.sendEmptyMessageDelayed(0, 1000);
            }
        };
    }


    @Override
    protected void onResume() {
        super.onResume();
        mHandler.sendEmptyMessageDelayed(0, 1000);
    }

    @Override
    protected void onPause() {
        super.onPause();
        mHandler.removeMessages(0);
    }

    @Override
    protected void onDestroy() {
        mThread.quit();
        super.onDestroy();
    }
}

由於HandlerTHread的run方法是無限循環,所以有必要去調用它的quit方法或者quitSafely方法去退出,這是一個良好的編程習慣。

V. IntentService

IntentService是一種比較特殊的Service,它繼承自Service,是一個抽象類,因此只有它的實現類才能使用。它內部封裝了HandlerThread和Handler,從它的構造方法就可以看出來:

@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);
}

從這個構造方法中我們可以看到首先創建了一個HandlerThread,然後用它的Looper來創建了一個Handler,這就意味着IntentService的操作邏輯是在HandlerThread中執行了,所以IntetnService是可以用來做耗時操作的,另外由於它是Service的原因,所以它的優先級是要比普通的Thread高的。IntentService首次啓動會調用這個構造方法,每次啓動都會回調onStartCommad方法,看看onStartCommad方法:

@Override
public void onStart(@Nullable Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}

/**
 * You should not override this method for your IntentService. Instead,
 * override {@link #onHandleIntent}, which the system calls when the IntentService
 * receives a start request.
 * @see android.app.Service#onStartCommand
 */
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

onStartCommad方法會調onStart方法,在onStart方法中,將消息發送到了mServiceHandler中

private final class ServiceHandler extends Handler {
    public ServiceHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        onHandleIntent((Intent)msg.obj);
        stopSelf(msg.arg1);
    }
}

當消息到達handleMessage會繼續掉onHandleIntent方法,當這個onHandleIntent方法執行結束,會調stopSelf(int startId)方法來停止服務,如果目前後多個後臺任務,那麼當onHandleIntent執行完最後一個任務時,stopSelf(int startId)纔會直接停止服務。

@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);

這個方法是一個抽象方法,我們在自己的實現類中實現這個方法,在這個方法中做我們的操作邏輯。

例子:

public class LocalIntentService extends IntentService {

    private static final String TAG = "LocalIntentService";

    public LocalIntentService() {
        super(TAG);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        String action = intent.getStringExtra("action");
        SystemClock.sleep(1000);
        Log.d(TAG, "onHandleIntent: " + action);
    }


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

        Log.d(TAG, "LocalIntentService was Destroy");
    }
}

別忘了這是個Service,必須要在清單文件<application>.....</application>裏面註冊的。
<service android:name=".LocalIntentService"/>

執行如下代碼

Intent intent = new Intent(this, LocalIntentService.class);
intent.putExtra("action","task1");
startService(intent);

intent.putExtra("action","task2");
startService(intent);

intent.putExtra("action","task2");
startService(intent);

可以看到是三個任務結束後才停止服務的。

VI. Android中的線程池

1)使用線程池的好處
①重用線程池中的線程,避免因爲線程的創建和銷燬帶來的系統性能開銷;
②能有效控制線程池的最大併發數,避免大量線程之間因互相搶佔系統資源而導致阻塞的現象;
③能夠對線程進行簡單的管理,並提供定時執行和指定間隔時間循環執行等功能。

2)ThreadPoolExecutor
android中的線程池概念來源於java中的Executor,而Executor是一個接口,真正線程池的實現 是ThreadPoolExecutor,Android下的線程池主要分爲4類,都是直接或間接配置ThreadPoolExecutor來實現的,看看ThreadPoolExecutor構造方法的參數:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {}

①corePoolSize:線程池的核心線程數,默認情況下,核心線程在線程池中是一直存活的,即使它們處於空閒的狀態。如果將ThreadPoolExecutor的allowCoreThreadTimeOut設置爲ture,那空閒的核心線程在等待任務的過程中就會有超時策略,時間有keepAliveTime參數所指定,當等待時間超過keepAliveTime所指定的時間後,核心線程就會被終止;

②maximumPoolSize:線程池所能容納的最大線程數,包括核心和非核心線程,當活動線程到達最大值,後續的任務就會被阻塞;

③keepAliveTiem:非核心線程閒置時的超時時長,當非核心線程空閒時間超過這個值,就會被終止。如果ThreadPoolExecutor的allowCoreThreadTimeOut設置爲true,這個時間同樣會作用在覈心線程上;
④unit:keepAliveTime時間單位,這是一個枚舉,如:TimeUnit.DAYS等;

⑤workQueue:線程池中的任務隊列,通過execute方法提交的Runable對象會存儲在這個參數中;

⑥threadFactory:線程工廠,提供線程的創建功能;

⑦另外還有一個不常用的參數 RejectedExecutionHandler handler,在線程數量和任務隊列到達上限時,會通過這個handler通知外界異常。

VII. 線程池的的執行規則

1)如果線程池中的線程數量未到達核心線程的數量,那麼會直接啓動一個核心線程來處理任務;
2)如果線程池中的線程數已經達到或者超過核心線程數,那麼任務會被插入到線程池的任務隊列中排隊等候;
3)如果步驟2中無法插入到任務隊列中(原因可能是任務隊列已經滿了),這個時候如果線程池中的線程數未達到最大容量,則會直接啓動一個非核心線程來處理任務;
4)如果步驟3中線程數量已經達到最大值,那麼就拒絕執行任務, 這個時候就會通過 RejectedExecutionHandler handler這個參數來通知調用者。

VIII. 線程池的分類

FixedThreadPool

通過Executors的newFixedThreadPool方法來創建,這種線程池只有核心線程,沒有超時策略,當所有線程處於活動狀態時,任務會處於等待狀態。由於這種線程池只有核心線程,並且即使空閒也不會被回收(除非線程池被回收),這就意味着它能夠快速響應外界的請求。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

通過newFixedThreadPool方法的實現可以看到,線程池的最大容量=核心線程數(nThreads),沒有超時策略。

CachedThreadPool

通過Executors的newCachedThreadPool方法來創建

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

通過這個方法的實現可以發現,這中線程池沒有核心線程,並且最大線程數量是Integer.MAX_VALUE,這就意味着沒有線程數量的限制,空閒超時時間爲60秒。new SynchronousQueue這個任務隊列比較特殊,很多時候可以簡單理解爲一個無法存儲的任務隊列。也就是說,當有新的任務到來,如果有空閒的線程,則將任務給空閒的線程處理,否則直接創建一個新的線程來處理任務。這將導致任何任務將被立即執行。從它的特性來看,比較適合用來處理大量的耗時較短的任務。

ScheduledThreadPool

通過Executors的newScheduledThreadPool方法來創建

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}

private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;

通過這個方法的實現,可以看到,指定了核心線程數量,非核心線程數沒有限制,非核心線程超時時間爲10毫秒(相當於空閒後立即回收)。這類線程池適用於執行定時的任務和固定週期的重複任務。

SingleThreadPool

通過Executors的newSingleThreadExecutor方法來創建

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

這種線程池只有一個核心線程,它確保所有任務在一個線程中順序執行。SingleThreadExecutor的意義在於,統一所有的任務在一個線程中處理,這使得這些任務之間不需要處理線程同步問題。

IX. 4種線程池的演示例子

Runnable command = new Runnable() {
    @Override
    public void run() {
        SystemClock.sleep(1000);
        Log.d(TAG, "command is running");
    }
};

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
fixedThreadPool.execute(command);


ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(command);

ScheduledExecutorService scheduledThreadPool =  Executors.newScheduledThreadPool(4);
//延時兩秒後執行
scheduledThreadPool.schedule(command, 2000, java.util.concurrent.TimeUnit.MILLISECONDS);

ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();

singleThreadPool.execute(command);

X. 我們在前面說到,AsyncTask使用到了線程池,我們去看看它的線程池是怎麼樣的

private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;

private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger(1);

    public Thread newThread(Runnable r) {
        return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
    }
};

private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);

/**
 * An {@link Executor} that can be used to execute tasks in parallel.
 */
public static final Executor THREAD_POOL_EXECUTOR;

static {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
            sPoolWorkQueue, sThreadFactory);
    threadPoolExecutor.allowCoreThreadTimeOut(true);
    THREAD_POOL_EXECUTOR = threadPoolExecutor;
}

①核心線程數: Math.max(2, Math.min(CPU_COUNT - 1, 4)),其中CPU_COUNT 是CUP的核數,CPU_COUNT = Runtime.getRuntime().availableProcessors();
②最大線程數:CPU_COUNT * 2 + 1
③超時時間:30S

以上源碼均來自Android-24

最近搭建了個人博客,個人博客原文地址

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