android 線程池使用就是這么簡單

線程

線程在Android中是個很重要的概念,從用途來說,線程分為主線程與子線程,主線程用于處理界面相關事情,子線程用于執行耗時操作。除了Thread本身外,AysncTask,IntentService及HandleThread在安卓了都扮演著線程的角色。
AysncTask:底層封裝了線程池和Handler,方便開發者在子線程中更新ui。
Handler:具有消息循環的線程,內部可以使用Handler。
IntentService:是一個服務,可執行后臺任務,內部采用了HandlerThread來執行任務,任務執行完畢IntentService會自動退出。
傳統線程的缺陷
1:在任務眾多的情況下,系統要為每一個任務創建一個線程,而任務執行完畢后會銷毀每一個線程,所以會造成線程頻繁地創建與銷毀。
2:多個線程頻繁地創建會占用大量的資源,并且在資源競爭的時候就容易出現問題,同時這么多的線程缺乏一個統一的管理,容易造成界面的卡頓。
3:多個線程頻繁地銷毀,會頻繁地調用GC機制,這會使性能降低,又非常耗時。

線程池

線程池概念來源于Java中的Executor,它是一個接口,真正的實現為ThreadPoolExecutor。ThreadPoolExecutor提供了一系列參數來配置線程池。
優點
1:重用線程池中的線程,線程在執行完任務后不會立刻銷毀,而會等待另外的任務,這樣就不會頻繁地創建、銷毀線程和調用GC。。
2:有效控制線程池的最大并發數,避免大量線程搶占資源出現的問題。
3:對多個線程進行統一地管理,可提供定時執行及指定間隔循環執行的功能。
ThreadPoolExecutor
ThreadPoolExecutor 有多個重載方法,但最終都調用了這個構造方法。

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

corePoolSize: 線程池中核心線程的數量。
maximumPoolSize:線程池中最大線程數量。
keepAliveTime:非核心線程的超時時長,當系統中非核心線程閑置時間超過keepAliveTime之后,則會被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置為true,則該參數也表示核心線程的超時時長。
unit:keepAliveTime這個參數的單位,有納秒、微秒、毫秒、秒、分、時、天等。
workQueue:線程池中的任務隊列,該隊列主要用來存儲已經被提交但是尚未執行的任務。存儲在這里的任務是由ThreadPoolExecutor的execute方法提交來的。
threadFactory:為線程池提供創建新線程的功能,這個我們一般使用默認即可。
**handler: 拒絕策略,當線程無法執行新任務時(一般是由于線程池中的線程數量已經達到最大數或者線程池關閉導致的),默認情況下,當線程池無法處理新線程時,會拋出一個RejectedExecutionException。
執行
ThreadPoolExecutor有兩個方法可以供我們執行,分別是submit()和execute(),我們先來看看這兩個方法到底有什么差異。
execute()方法源碼:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        //獲得當前線程的生命周期對應的二進制狀態碼
        int c = ctl.get();
        //判斷當前線程數量是否小于核心線程數量,如果小于就直接創建核心 線程執行任務,創建成功直接跳出,失敗則接著往下走.
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //判斷線程池是否為RUNNING狀態,并且將任務添加至隊列中.
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //審核下線程池的狀態,如果不是RUNNING狀態,直接移除隊列中
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //如果當前線程數量為0,則單獨創建線程,而不指定任務.
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //如果不滿足上述條件,嘗試創建一個非核心線程來執行任務,如果創建失敗,調用reject()方法.
        else if (!addWorker(command, false))
            reject(command);
    }

submit()方法源碼:

public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        //還是通過調用execute
        execute(ftask);
        //最后會將包裝好的Runable返回
        return ftask;
    }

    //將Callable<T> 包裝進FutureTask中
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

//可以看出FutureTask也是實現Runnable接口,因為RunableFuture本身就繼承了Runnabel接口
public class FutureTask<V> implements RunnableFuture<V> {
    .......
}

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

分析兩個方法的源碼得出

  • submit()其實還是需要調用execute()去執行任務的,不同是submit()將包裝好的任務進行了返回,他會返回一個Future對象。
  • execute()方法中,不難看出addWorker()方法, 是創建線程(核心線程,非核心線程)的主要方法,而reject()方法為線程創建失敗的回調。

所以,通常情況下,在不需要線程執行返回結果值時,我們使用execute 方法。 而當我們需要返回值時,則使用submit方法,他會返回一個Future對象。Future不僅僅可以獲得一個結果,他還可以被取消,我們可以通過調用future的cancel()方法,取消一個Future的執行。 比如我們加入了一個線程,但是在這過程中我們又想中斷它,則可通過sumbit 來實現。
簡單例子
創建一個基本的線程池

//創建基本線程池
final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,5,1,TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(50));

使用線程池執行任務

for (int i = 0; i < 30; i++) {
     final int finali = i;
     Runnable runnable = new Runnable() {
             @Override
              public void run() {
                   try {
                        Thread.sleep(3000);
                        Log.e("TAG","run : "+finali+"  當前線程:"+Thread.currentThread().getName());
                   } catch (InterruptedException e) {
                         e.printStackTrace();
                   }
               }
      };
          threadPoolExecutor.execute(runnable);
}

結果是每3s打印三次日志。
ThreadPoolExecutor 執行任務時大致遵循如下流程:

  • 1.如果線程池中的線程數未達到核心線程數,則會立馬啟用一個核心線程去執行。
  • 2.如果線程池中的線程數已經達到核心線程數,且任務隊列workQueue未滿,則將新線程放入workQueue中等待執行。
  • 3.如果線程池中的線程數已經達到核心線程數但未超過線程池規定最大值,且workQueue已滿,則開啟一個非核心線程來執行任務。
  • 4.如果線程池中的線程數已經超過線程池規定最大值,則拒絕執行該任務,采取飽和策略,并拋出RejectedExecutionException異常。

上面例子中設置的任務隊列長度為50,任務只有30個,任務隊列未滿,只走到第二個流程,不會開啟額外的5-3=2個非核心線程,如果將任務隊列設為25,則前三個任務被核心線程執行,剩下的30-3=27個任務進入隊列會滿超出2個,此時會開啟2個非核心線程來執行剩下的兩個任務,這是剛好達到線程池的最大線程數,假如還有任務,將會拒絕執行,拋出RejectedExecutionException異常。

線程池的分類

FixedThreadPool (可重用固定線程數)

//源碼實現
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
//創建及執行
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
//執行上述Demo的runnable
fixedThreadPool.execute(runnable);

結果:每3s打印5次任務,跟上面的基礎線程池類似。
特點:參數為核心線程數,只有核心線程,無非核心線程無超時時長,并且阻塞隊列無界。
適用:執行長期的任務,性能好很多

SingleThreadPool(單個核線的fixed)

//源碼實現
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }//創建及執行
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
//執行上述Demo的runnable
singleThreadExecutor .execute(runnable);

結果:每3s打印1次任務。
特點:只有一個核心線程,當被占用時,其他的任務需要進入隊列等待,fixedThreadPool設置核心線程為1時就是SingleThreadPool。
適用:一個任務一個任務執行的場景

CachedThreadPool (按需創建)

//源碼實現
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
//創建及執行
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//執行上述Demo的runnable
cachedThreadPool.execute(runnable);

結果:3s后打印30次任務。
特點:沒有核心線程,只有非核心線程,并且每個非核心線程空閑等待的時間為60s,采用SynchronousQueue隊列。
結果分析:

  • 因為沒有核心線程,其他全為非核心線程,SynchronousQueue是不存儲元素的,每次插入操作必須伴隨一個移除操作,一個移除操作也要伴隨一個插入操作。
  • 當一個任務執行時,先用SynchronousQueue的offer提交任務,如果線程池中有線程空閑,則調用SynchronousQueue的poll方法來移除任務并交給線程處理;如果沒有線程空閑,則開啟一個新的非核心線程來處理任務。
  • 由于maximumPoolSize是Integer.MAX_VALUE,無界的,所以如果線程處理任務速度小于提交任務的速度,則會不斷地創建新的線程,這時需要注意不要過度創建,應采取措施調整雙方速度,不然線程創建太多會影響性能。
  • 從其特點可以看出,CachedThreadPool適用于有大量需要立即執行的耗時少的任務的情況。
    適用:執行很多短期異步的小程序或者負載較輕的服務器

ScheduledThreadPool(定時延時執行)

//源碼實現
 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());
    }

特點:核心線程數量是固定的,非核心線程無窮大。當非核心線程閑置時,則會被立即回收。
ScheduledThreadPool也是四個當中唯一一個具有定時定期執行任務功能的線程池。它適合執行一些周期性任務或者延時任務。
適用:一個任務一個任務執行的場景

//創建及執行
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
//延遲5秒執行
scheduledExecutorService.schedule(runnable, 5, TimeUnit.SECONDS);
//延遲5s后啟動,每1s執行一次             scheduledExecutorService.scheduleAtFixedRate(runnable,5,1,TimeUnit.SECONDS);
//啟動后第一次延遲5s執行,后面延遲1s執行  scheduledExecutorService.scheduleWithFixedDelay(runnable,5,1,TimeUnit.SECONDS);

線程池中的任務的終止

一般線程執行完run方法之后,線程就正常結束了,線程池中的任務可以用下面的方式來實現:
利用 Future 和 Callable
步驟:

  • 實現 Callable 接口
  • 調用 pool.submit() 方法,返回 Future 對象
  • 用 Future 對象來獲取線程的狀態。
private void cancelAThread() {
        ExecutorService pool = Executors.newFixedThreadPool(2);
          
          Callable<String> callable = new Callable<String>() {
              
            @Override
            public String call() throws Exception {
                System.out.println("test");
                return "true";
            }
        };
          
        Future<String> f = pool.submit(callable);
          
        System.out.println(f.isCancelled());
        System.out.println(f.isDone());
        f.cancel(true);
  
    }

線程池的其他常用方法

1.shutDown()  關閉線程池,不影響已經提交的任務

2.shutDownNow() 關閉線程池,并嘗試去終止正在執行的線程

3.allowCoreThreadTimeOut(boolean value) 允許核心線程閑置超時時被回收

4.submit 一般情況下我們使用execute來提交任務,但是有時候可能也會用到submit,使用submit的好處是submit有返回值。

5.beforeExecute() - 任務執行前執行的方法

6.afterExecute() -任務執行結束后執行的方法

7.terminated() -線程池關閉后執行的方法

參考出處鏈接:
Android 線程池原理及使用
Android 線程池ThreadPoolExecutor詳解

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容