第19周—Android的線程和線程池

一、線程

主線程

  • 主線程是指進程中所擁有的線程,在Java中默認情況下一個進程只有一個線程,這個線程就是主線程。
  • 主線程主要作用是運行四大組件以及處理它們和用戶的交互,因為用戶隨時會和界面發生交互,所以主線程在任何時候都必須有較高的響應速度,否則會產生一種界面卡頓的感覺,極大的影響用戶體驗。

子線程

  • 工作線程,除了主線程以外的線程都是子線程。
  • 子線程的作用是執行耗時任務,比如網絡請求、I/O操作等,從Android 3.0開始系統要求網絡訪問必須在子線程中進行,否則網絡訪問將會失敗并拋出NetWorkOnMainThreadException這個異常,這樣做是為了避免主線程由于被耗時操作所阻塞從而出現ANR現象。

ANR異常

  • 什么是ANR?

    • 應用程序無響應(Application Not Responding)。
    • 應用程序的響應性是由Activity Manager和WindowManager系統服務監視的。
  • 造成ANR的主要原因:

    • 主線程被IO操作(從4.0之后網絡IO不允許在主線程中)阻塞。
    • 主線程中存在耗時的計算。
  • Android中哪些操作是在主線程的?

    • Activity的所有生命周期回調都是在主線程執行的。
    • Service默認是執行在主線程的。
    • BroadcastReceiver的onReceive回調是在主線程執行的。
    • 沒有使用子線程的looper的Handler的handleMessage,post(Runnable)是在主線程執行的。
    • AsyncTask的回調中除了doInBackground,其他都是在主線程執行的。
  • 如何解決ANR?

    • 使用Asynctask處理耗時IO操作。
    • 使用Thread或者HandlerThread提高優先級。
    • 使用Handler來處理工作線程的耗時任務。
    • Activity的onCreate和onResume回調盡量避免耗時的代碼。

二、Android中的線程形態

Thread

AsyncTask

HandlerThread

  • HandlerThread繼承了Thread,它是一種可以使用Handler的Thread,它的實現也很簡單,就是在run方法中通過Looper.prepare()來創建消息隊列,并通過Looper.loop()來開啟消息循環,這樣在實際的使用中就允許在HandlerThread中創建Handler。HandlerThread的run方法如下所示:
    
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
  • 從HandlerThread的實現來看,普通Thread主要用于在run方法中執行一個耗時任務,而HandlerThread在內部創建了消息隊列,外界需要通過Handler的消息方式來通知HanlderThread執行一個具體的任務。
  • HandlerThread的run方法是一個無限循環,因此當明確不需要再使用HandlerThread時,可以通過它的quit或者quitSafely方法來終止線程的執行,這是一個良好的編程習慣。
  • HandlerThread在Android中的一個具體的使用場景是IntentService。

IntentService

  • IntentService是一種特殊的Service,它繼承了Service并且它是一個抽象類,因此必須創建它的子類才能使用IntentService。
  • IntentService可用于執行后臺耗時的任務,當任務執行后它會自動停止,同時由于IntentService是服務的原因,這導致它的優先級比單純的線程要高很多,所有IntentService比較適合執行一些高優先級的后臺任務,因為它優先級高不容易被系統殺死。
  • IntentService封裝了HandlerThread和Handler,這一點可以從它的onCreate方法中看出來,如下所示:
    @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);
    }
  • 當IntentService被第一次啟動時,它的onCreate方法會被調用,onCreate方法會創建一個HandlerThread,然后使用它的Looper來構造一個Handler對象mServiceHandler,這樣通過mServiceHandler發送的消息最終都會在HandlerThread中執行,從這個角度來看,IntentService也可以用于執行后臺任務。每次啟動IntentService,它的onStartCommand方法就會調用一次,IntentService在onStartCommand中處理每個后臺任務的Intent。
   @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }
  • onStartCommand方法處理外界的Intent,是通過調用了onStart方法,具體實現如下所示:
    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }
  • 可以看出,IntentService僅僅是通過mServiceHandler發送了一個消息,這個消息會在HandlerThread中被處理,mServiceHandler收到消息后,會將Intent對象傳遞給onHandlerIntent方法去處理,注意這個Intent對象的內容和外界的startService(intent)中的intent的內容是完全一致的,通過這個Intent對象即可解析出外界啟動IntentService時所傳遞的參數,通過這些參數就可以區分具體的后臺任務,這樣在onHandlerIntent方法中就可以對不同的后臺任務做處理了。當onHandlerIntent方法執行結束后,IntentService會通過stopSelf(int startId)方法來嘗試停止服務。這里之所以采用stopSelf(int startId)而不是stopSelf()來停止服務,那是因為stopSelf()會立刻停止服務,而這個時候可能還有其他消息未處理,stopSelf(int startId)則會等待所有的消息都處理完畢后才終止服務。一般來說,stopSelf(int startId)在嘗試停止服務之前會判斷最近啟動服務的次數是否和startId相等,如果相等就立刻停止服務,不相等則不停止服務,這個策略可以從AMS的stopServiceToken方法的實現中找到依據,ServiceHandler的實現如下所示:
    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);
        }
    }

IntentService的onHandlerIntent方法是一個抽象方法,它需要我們在子類中實現,它的作用從Intent參數中區分具體的任務并執行這些任務。如果目前只存在一個后臺任務,那么onHandleIntent方法執行完這個任務后,stopSelf(int startId)就會直接停止服務。如果目前存在多個后臺任務,那么當onHandleIntent方法執行完最后一個任務時,stopSelf(int startId)才會直接停止服務。另外,由于每執行一個后臺任務就必須啟動一次IntentService,而IntentService內部則通過消息的方式向HandlerThread請求執行任務,Handler中的Looper是順序處理消息的,這就意味著IntentService也是順序執行后臺任務的,當有多個后臺任務同時存在時,這些后臺任務會按照外界發起的順序排隊執行。

  • 下面通過一個示例來進一步說明IntentService的工作方式,首先派生一個IntentService的子類,比如LocalIntentService,它的實現如下所示:
    public class LocaIntentService extends IntentService {

        private static final String TAG = "LocalIntentService";

        /**
         * Creates an IntentService.  Invoked by your subclass's constructor.
         *
         * @param name Used to name the worker thread, important only for debugging.
         */
        public LocaIntentService(String name) {
            super(name);
        }

        @Override
        protected void onHandleIntent(@Nullable Intent intent) {
            String action = intent.getStringExtra("task_action");
            Log.d(TAG, "receive task:" + action);
            SystemClock.sleep(3000);
            if ("com.ryq.action.TASK1".equals(action)) {
                Log.d(TAG, "handle task:" + action);
            }
        }

        @Override
        public void onDestroy() {
            Log.d(TAG, "service destroyed.");
            super.onDestroy();
            
        }
    }

這里對LocalIntentService的實現做一下簡單的說明,在onHandleIntent方法中會從參數中解析出后臺任務的標識,即task_action字段所代表的內容,然后根據不同的任務標識來執行具體的后臺任務,這里為了簡單起見,直接通過SystemClock.sleep(3000)來休眠3000毫秒從而模擬一種耗時的后臺任務,另外為了驗證IntentService的停止時機,這里在onDestroy()中打印了一句日志,LocalIntentService實現完成了以后,就可以在外界請求執行后臺任務,在下面的代碼中先后發起了3個后臺任務的請求:

       Intent service = new Intent(this, LocaIntentService.class);
        service.putExtra("task_action", "com.ryq.action.TASK1");
        startService(service);
        service.putExtra("task_action", "com.ryg.action.TASK2");
        startService(service);
        service.putExtra("task_action", "com.ryg.action.TASK3");
        startService(service);
        
  • 運行程序,觀察日志,如下所示。


  • 從上面的日志可以看出,三個后臺任務是排隊執行的,它們的執行順序就是它們發起的請求對的順序,即TASK1、TASK2、TASK3。另外一點就是當TASK3執行完畢后,LocalIntentService才真正地停止,從日志中可以看出LocalIntentService執行了onDestroy(),這也意味著服務正在停止。

三、Android中的線程池

線程池的優點有3點:

  • 重用線程池中的線程,避免因為線程的創建和銷毀所帶來的性能開銷。
  • 能有效控制線程池的最大并發數,避免大量的線程之間因互相搶占系統資源而導致的阻塞現象。
  • 能夠對線程進行簡單的管理,并提供定時執行以及指定間隔循環執行等功能。

ThreadPoolExecutor

  • Android中的線程池的概念來源于Java中的Executor,Executor是一個接口,真正的線程池實現為ThreadPoolExecutor。
  • ThreadPoolExecutor提供了一系列參數來配置線程池,通過不同的參數可以創建不同的線程池。
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory)
corePoolSize
  • 線程池的核心線程數,默認情況下,核心線程會在線程池中一直存活,即使它們處于閑置狀態。如果將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置為true,那么閑置的核心線程在等待新任務到來時會有超時策略,這個時間間隔由keepAliveTime所指定,當等待時間超過keepAliveTime所指定的時長后,核心線程就會被終止。
maximumPoolSize
  • 線程池所能容納的最大線程數,當活動線程數達到這個數值后,后續的新任務將會被阻塞。
keepAliveTime
  • 非核心線程閑置時的超時時長,超過這個時長,非核心線程就會被回收。當ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置為true時,keepAliveTime同樣會作用于核心線程。
unit
  • 用于指定keepAliveTime參數的時間單位,這是一個枚舉,常用的有TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分鐘)等。
workQueue
  • 線程池中的任務隊列,通過線程池的execute方法提交的Runnable對象會存儲在這個參數中。
threadFactory
  • 線程工廠,為線程提供創建新線程的功能。ThreadFactory是一個接口,它只有一個方法:Thread newThread(Runnable r)。
RejectedExecutionHandler handler。
  • 這是ThreadPoolExecutor的一個不常用的參數,當線程無法執行新任務時,這可能是由于任務隊列已滿或者是無法成功執行任務,這個時候ThreadPoolExecutor會調用handler的rejectedExecution方法來通知調用者,默認情況下rejectedExecutor方法會直接拋出一個RejectedExecutionException。ThreadPoolExecutor為RejectedExecutionHandler提供了幾個可選值:
    • CallerRunsPolicy
    • AbortPolicy
      • 默認值,它會直接拋出RejectedExecutionException。
    • DiscardPolicy
    • DiscardOldestPolicy
ThreadPoolExecutor執行任務時大致遵循如下規則:
  • 如果線程池中的線程數量未達到核心線程的數量,那么會直接啟動一個核心線程來執行任務。

  • 如果線程池中的線程數量已經達到或者超過核心線程的數量,那么任務會被插入到任務隊列中排隊等待執行。

  • 如果在步驟2中無法將任務插入到任務隊列中,這往往是由于任務隊列已滿,這個時候如果線程數量未達到線程池規定的最大值,那么會立刻啟動一個非核心線程來執行任務。

  • 如果步驟3中的線程數量已經達到線程池規定的最大值,那么就拒絕執行此任務,ThreadPoolExecutor會調用RejectedExecutionHandler的rejectedExecution方法來通知調用者。

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    // We want at least 2 threads and at most 4 threads in the core pool,
    // preferring to have 1 less than the CPU count to avoid saturating
    // the CPU with background work
    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;
    }
  • 從上面的代碼可以知道,AsyncTask對THREAD_POOL_EXECUTOR這個線程池進行了配置,配置后的線程池規格如下:
    • 核心線程數等于CPU 核心數+1。
    • 線程池的最大線程數為CPU核心數的2倍+1。
    • 核心線程無超時機制,非核心線程在閑置時的超時時間為1秒。
    • 任務隊列的容量為128。

線程池的分類

FixedThreadPool
  • 通過Executors的newFixedThreadPool方法來創建。
  • 它是一種線程數量固定的線程池,當線程處于空閑狀態時,它們并不會被回收,除非線程池被關閉了。當所有的線程都處于活動狀態時,新任務都會處于等待狀態,直到有線程空閑出來。
  • 它只有核心線程并且這些核心線程不會被回收,能夠更加快速地響應外界的請求。
    newFixedThreadPool方法的實現如下,可以發現FixedThreadPool中只有核心線程并且這些核心線程沒有超時機制,任務隊列沒有大小限制。
    public static ExecutorService newFixedThreadPool(int nThreads){
        return new ThreadPoolExecutor(nThreads, nThreads,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
    
        }
CachedThreadPool
  • 通過Executors的newCachedThreadPool方法來創建的。
  • 它是一種線程數量不定的線程池。
  • 它只有非核心線程,并且最大線程數為Integer.MAX_VALUE。由于Integer.MAX_VALUE是一個很大的數,實際上就相當于最大線程數可以任意大。
  • 當線程池中的線程都處于活動狀態時,線程池會創建新的線程來處理新任務,否則就會利用空閑的線程來處理新任務。線程池中的空閑線程都有超時機制,這個超時時長為60秒,超過60秒閑置線程就會被回收。
  • 和FixedThreadPool不同的是,CachedThreadPool的任務隊列其實相當于一個空集合,這將導致任何任務都會立即被執行,因為在這種場景下SynchronousQueue是無法插入任務的。
  • SynchronousQueue是一個非常特殊的隊列,在很多情況下可以把它簡單理解為一個無法存儲元素的隊列,由于它在實際中較少使用,這里就不深入探討它了。
  • 從CachedThreadPool的特性來看,這類線程池比較適合執行大量的耗時較少的任務,當整個線程池都處于閑置狀態時,線程池中的線程都會超時而被停止,這個時候CackedThreadPool之中實際上是沒有任何線程的。它幾乎是不占用任何系統資源。newCachedThreadPool方法的實現如下所示:
        public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                    60L, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>());
        }
ScheduledThreadPool
  • 通過Executors的newScheduledThreadPool方法來創建的。
  • 它的核心線程數量是固定的,而非核心線程數是沒有限制的,并且當非核心線程閑置時會被立即回收。
  • ScheduledThreadPool這類線程池主要用于執行定時任務和具有固定周期的重復任務,newScheduledThreadPool方法的實現如下所示:
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize,Integer.MAX_VALUE,0,NANOSECONDS,new DelayedWorkQueue());
    }
SingleThreadExecutor
  • 通過Executors的newSingleThreadExecutor方法來創建。
  • 這類線程池內部只有一個核心線程,它確保所有的任務都在同一個線程中按順序執行。
  • SingleThreadExecutor的意義在于統一所有的外界任務到一個線程中,這使得在這些任務之間不需要處理線程同步的問題,newSingleThreadExecutor方法的實現如下所示:
        
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService(
            new ThreadPoolExecutor(
                    1,
                    1,
                    0L,
                    TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>())
    );
    );
}
系統預置的4種線程池的典型使用方法,如下所示:
                
        Runnable command = new Runnable() {
               @Override
               public void run() {
                    SystemClock.sleep(2000);  
               }
                    
        };

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

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

        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
        //2000ms后執行command
        scheduledThreadPool.schedule(command, 2000, TimeUnit.MILLISECONDS);
        //延遲10ms后,每隔1000ms執行一次command
        scheduledThreadPool.scheduleAtFixedRate(command, 10, 1000, TimeUnit.MILLISECONDS);

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

推薦閱讀更多精彩內容

  • 1.ios高性能編程 (1).內層 最小的內層平均值和峰值(2).耗電量 高效的算法和數據結構(3).初始化時...
    歐辰_OSR閱讀 29,553評論 8 265
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,099評論 25 708
  • 我想編一部話劇 話劇是你我的故事 從相遇,相知,相愛到結局 一切都按照我的旨意 我不會讓人挑撥我們的關系 亦不會安...
    千夢冰雁閱讀 582評論 20 8
  • Anna艷娜 2018年2月22日復盤 我的目標管理:智慧·泛學 / 健康·美麗 / 娛樂·休閑 / 家人·朋友 ...
    Anna艷娜閱讀 179評論 0 0
  • GCD(Grand Central Dispatch) 核心是dispatch queue,隊列就是一系列的代碼塊...
    伽藍香閱讀 229評論 0 0