【多線程】Android多線程學習筆記——線程池

前言

       轉載請聲明,轉自【https://www.cnblogs.com/andy-songwei/p/15313312.html】,謝謝!

      Java線程池採用了享元設計模式,在系統中維持一定數量的線程,用於處理異步或併發需求,在平時處理異步或併發任務時被廣泛使用。這裏基於JDK1.8和Android28來整理一些關於線程池的知識點。本篇主要包含如下內容:

 

 

 

一、合理使用線程池的好處

    (1)降低資源消耗。 重用線程池,可以降低頻繁創建和銷燬線程所帶來的消耗。

    (2)提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。假設一個服務器完成一項任務所需時間爲:T1 創建線程時間,T2 在線程中執行任務的時間,T3 銷燬線程時間。如果:T1 + T3 遠大於 T2,則可以採用線程池,以提高程序的性能。線程池技術正是關注如何縮短或調整T1,T3時間的技術,從而提高程序性能的。它把T1,T3分別安排在程序的啓動和結束的時間段或者一些空閒的時間段,這樣在程序處理客戶請求時,不會有T1,T3的開銷了

    (3)提高線程的可管理性。線程是稀缺資源,如果無限制地創建,不僅會消耗系統資源,還可能導致大量的線程之間因互相搶佔系統資源而導致阻塞。使用線程池可以進行統一分配、調優和監控。

 

二、Jdk提供的線程池框架

       Java中提供的線程池框架中,主要類的關係及結構如下圖所示:

  •  Executor是一個接口,它是Executor框架的基礎,它將任務的提交與任務的執行分離開來。
  • ExecutorService接口繼承了Executor,在其上做了一些shutdown()、submit()的擴展,可以說是真正的線程池接口;
  • AbstractExecutorService抽象類實現了ExecutorService接口中的大部分方法;
  • ThreadPoolExecutor是線程池的核心實現類,用來執行被提交的任務。
  • ScheduledExecutorService接口繼承了ExecutorService接口,提供了帶"週期執行"功能ExecutorService;
  • ScheduledThreadPoolExecutor是一個實現類,可以在給定的延遲後運行命令,或者定期執行命令。ScheduledThreadPoolExecutor比Timer更靈活,功能更強大。

 

三、線程池創建中各個參數的含義

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

      線程池的核心線程數,默認情況下,核心線程會在線程池中一直存活,即使它們處於閒置狀態。如果將ThreadPoolExecutor中的allowCoreThreadTimeOut屬性設置爲true,那麼閒置的核心線程在等待新任務到來時會有超時策略,這個時間間隔由keepAliveTime和unit所指定的時長決定,超過這個時長後核心線程就會被終止。

      在默認情況下,當提交一個任務時,線程池會創建一個新線程執行任務,直到當前線程數等於corePoolSize,而如果執行了ThreadPoolExecutor的prestartAllCoreThreads()方法,線程會提前創建並啓動所有核心線程;如果當前線程數爲corePoolSize,繼續提交的任務會被保存到阻塞隊列中,等待被執行。

  •   maximumPoolSize

    線程池所能容納的最大線程數。當阻塞隊列(即參數workQueue)滿了後,如果繼續提交任務,則創建新的線程執行任務,直到該線程池中所有活動的線程數達到maximumPoolSize。如果線程池中的活動線程數達到maximumPoolSize,後續還有新任務,就會執行線程池的拒絕策略,由另外一個參數RejectedExecutionHandler來確定。

  • keepAliveTime

     非核心線程閒置時的超時時長,超過這個時長,非核心線程就會被回收。默認情況下,該參數只在線程數大於corePoolSize時纔有用,而當ThreadPoolExecutor中的allowCoreThreadTimeOut屬性設置爲true時,keepAliveTime同樣會作用於核心線程。

  • unit

    用於指定keepAliveTime參數的時間單位,這是一個枚舉,常用的有TimeUnit.MILLISECONDS、TimeUnit.SECONDS、TimeUnit.MINUTES等。

  • workQueue

       線程池的阻塞隊列,通過線程池的execute方法提交的Runnable對象會存儲在這個參數中。當線程池中的線程數超過它的corePoolSize的時候,線程會進入阻塞隊列進行阻塞等待。通過workQueue,線程池實現了阻塞功能。常用阻塞隊列有:

       1)ArrayBlockingQueue:一個由數組結構組成的有界阻塞隊列。此隊列按照先進先出(FIFO)的原則對元素進行排序。默認情況下不保證線程公平的訪問隊列,所謂公平訪問隊列是指阻塞的線程,可以按照阻塞的先後順序訪問隊列,即先阻塞線程先訪問隊列。非公平性是對先等待的線程是非公平的,當隊列可用時,阻塞的線程都可以爭奪訪問隊列的資格,有可能先阻塞的線程最後才訪問隊列。初始化時有參數可以設置

       2)LinkedBlockingQueue:一個由鏈表結構組成的有界阻塞隊列(常用)。此隊列的默認和最大長度爲Integer.MAX_VALUE,它按照先進先出的原則對元素進行排序。

      3)PriorityBlockingQueue:一個支持優先級排序的無界阻塞隊列。

      4)DelayQueue:一個使用優先級隊列實現的無界阻塞隊列,支持延時獲取元素,隊列使用PriorityQueue來實現。隊列中的元素必須實現Delayed接口,在創建元素時可以指定多久才能從隊列中獲取當前元素。只有在延遲期滿時才能從隊列中提取元素。DelayQueue非常有用,可以將DelayQueue運用在緩存系統的設計可以用DelayQueue保存緩存元素的有效期,使用一個線程循環查詢DelayQueue,一旦能從DelayQueue中獲取元素時,表示緩存有效期到了。

      5)SynchronousQueue:一個不存儲元素的阻塞隊列(常用)。每一個put操作必須等待一個take操作,否則不能繼續添加元素。SynchronousQueue可以看成是一個傳球手,負責把生產者線程處理的數據直接傳遞給消費者線程。隊列本身並不存儲任何元素,非常適合傳遞性場景。

      6)LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。

      7)LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。

以上的阻塞隊列都實現了BlockingQueue接口,也都是線程安全的。

       有界隊列就是長度有限,滿了以後生產者會阻塞,無界隊列就是裏面能放無數的東西而不會因爲隊列長度限制被阻塞,當然空間限制來源於系統資源的限制,如果處理不及時,導致隊列越來越大越來越大,超出一定的限制致使內存超限,操作系統或者JVM幫你解決煩惱,直接把你 OOM kill 省事了。無界也會阻塞,爲何?因爲阻塞不僅僅體現在生產者放入元素時會阻塞,消費者拿取元素時,如果沒有元素,同樣也會阻塞。

       一般來說,我們應該儘量使用有界隊列,因爲使用無界隊列作爲工作隊列會對線程池帶來如下影響:

      1)當線程池中的線程數達到corePoolSize後,新任務將在無界隊列中等待,因此線程池中的線程數不會超過corePoolSize。

      2)由於1,使用無界隊列時maximumPoolSize將是一個無效參數。

      3)由於1和2,使用無界隊列時keepAliveTime將是一個無效參數。

      4)更重要的,使用無界queue可能會耗盡系統資源,有界隊列則有助於防止資源耗盡,同時即使使用有界隊列,也要儘量控制隊列的大小在一個合適的範圍。

  • threadFactory

    線程工廠,爲線程池提供創建新線程的功能。ThreadFactory是一個接口: 

public interface ThreadFactory {
    Thread newThread(Runnable r);
}

       通過自定義的線程工廠可以給每個新建的線程設置一個具有識別度的線程名,當然還可以更加自由的對線程做更多的設置,比如設置所有的線程爲守護線程。Executors靜態工廠裏默認的threadFactory,線程的命名規則是“pool-數字-thread-數字”。

 1 //java.util.concurrent.Executors.java
 2 static class DefaultThreadFactory implements ThreadFactory {
 3     private static final AtomicInteger poolNumber = new AtomicInteger(1);
 4     private final ThreadGroup group;
 5     private final AtomicInteger threadNumber = new AtomicInteger(1);
 6     private final String namePrefix;
 7     DefaultThreadFactory() {
 8         SecurityManager s = System.getSecurityManager();
 9         group = (s != null) ? s.getThreadGroup() :
10                               Thread.currentThread().getThreadGroup();
11         namePrefix = "pool-" +
12                       poolNumber.getAndIncrement() +
13                      "-thread-";
14     }
15     public Thread newThread(Runnable r) {
16         Thread t = new Thread(group, r,
17                               namePrefix + threadNumber.getAndIncrement(),
18                               0);
19         if (t.isDaemon())
20             t.setDaemon(false);
21         if (t.getPriority() != Thread.NORM_PRIORITY)
22             t.setPriority(Thread.NORM_PRIORITY);
23         return t;
24     }
25 }
  • handler

      線程池的飽和策略,當阻塞隊列滿了,且沒有空閒的工作線程,如果繼續提交任務,必須採取一種策略處理該任務,線程池提供了4種策略:

(1)AbortPolicy:直接拋出RejectedException異常,默認策略;

(2)CallerRunsPolicy:用調用者所在的線程來執行任務;

(3)DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,並執行當前任務;

(4)DiscardPolicy:直接丟棄任務;

當然也可以根據應用場景實現RejectedExecutionHandler接口,自定義飽和策略,如記錄日誌或持久化存儲不能處理的任務。

 

四、線程池的工作機制

      1)如果當前運行的線程少於corePoolSize,則創建新線程來執行任務(注意,執行這一步驟需要獲取全局鎖)。

      2)如果運行的線程等於corePoolSize後仍然有任務提交,則將任務加入BlockingQueue。

      3)如果無法將任務加入BlockingQueue(隊列已滿),則創建新的線程來處理任務,其上限是當前活動線程數不超過maximumPoolSize。

      4)如果創建新線程時,當前運行的線程超出maximumPoolSize,任務將被拒絕,並調用RejectedExecutionHandler.rejectedExecution()方法。

 

五、提交任務

       execute()方法用於提交不需要返回值的任務,所以無法判斷任務是否被線程池執行成功。

       submit()方法用於提交需要返回值的任務。線程池會返回一個future類型的對象,通過這個future對象可以判斷任務是否執行成功,並且可以通過future的get()方法來獲取返回值,get()方法會阻塞當前線程直到任務完成,而使用get(long timeout,TimeUnit unit)方法則會阻塞當前線程一段時間後立即返回,這時候有可能任務沒有執行完。

 

六、關閉線程池

       可以通過調用線程池的shutdown或shutdownNow方法來關閉線程池。它們的原理是遍歷線程池中的工作線程,然後逐個調用線程的interrupt方法來中斷線程,所以無法響應中斷的任務可能永遠無法終止。但是它們存在一定的區別,shutdownNow首先將線程池的狀態設置成STOP,然後嘗試停止所有的正在執行或暫停任務的線程,並返回等待執行任務的列表,而shutdown只是將線程池的狀態設置成SHUTDOWN狀態,然後中斷所有沒有正在執行任務的線程。

       只要調用了這兩個關閉方法中的任意一個,isShutdown方法就會返回true。當所有的任務都已關閉後,才表示線程池關閉成功,這時調用isTerminaed方法會返回true。至於應該調用哪一種方法來關閉線程池,應該由提交到線程池的任務特性決定,通常調用shutdown方法來關閉線程池,如果任務不一定要執行完,則可以調用shutdownNow方法

 

七、 合理地配置線程池

要想合理地配置線程池,就必須首先分析任務特性,可以從以下幾個角度來分析。

    (1)任務的性質:CPU密集型任務、IO密集型任務和混合型任務。

    (2)任務的優先級:高、中和低。

    (3)任務的執行時間:長、中和短。

    (4)任務的依賴性:是否依賴其他系統資源,如數據庫連接。

       性質不同的任務可以用不同規模的線程池分開處理。

       CPU密集型任務應配置儘可能小的線程,如配置Ncpu+1(這爲什麼有個+1呢?這是因爲在現代計算機中引入了虛擬內存技術,cpu讀取真實內存時可能該數據不在真實內存中,需要到虛擬內存中去取,這叫內存缺頁。而cpu讀取虛擬內存的速度遠小於讀取真實內存的速度,此時cpu只能等待。+1之後多了一個線程,當出現內存缺頁時,正在等待的cpu可以去執行這多出來的線程,從而提高cpu的使用率)個線程的線程池。由於IO密集型任務線程並不是一直在執行任務,則應配置儘可能多的線程,如2*Ncpu。

        混合型的任務,如果可以拆分,將其拆分成一個CPU密集型任務和一個IO密集型任務,只要這兩個任務執行的時間相差不是太大,那麼分解後執行的吞吐量將高於串行執行的吞吐量。如果這兩個任務執行時間相差太大,則沒必要進行分解。可以通過Runtime.getRuntime().availableProcessors()方法獲得當前設備的CPU個數。

       優先級不同的任務可以使用優先級隊列PriorityBlockingQueue來處理。它可以讓優先級高的任務先執行。

       執行時間不同的任務可以交給不同規模的線程池來處理,或者可以使用優先級隊列,讓執行時間短的任務先執行。

       建議使用有界隊列。有界隊列能增加系統的穩定性和預警能力,可以根據需要設大一點兒,比如幾千。如果當時我們設置成無界隊列,那麼線程池的隊列就會越來越多,有可能會撐滿內存,導致整個系統不可用,而不只是後臺任務出現問題。

 

八、JDK中內置的4類線程池

       在JDK中的Executors類中提供了4類已經配置好的線程池:

 其源碼爲:

 1 //java.util.concurrent.Executors.java
 2 public class Executors {
 3     ......
 4     //提供的默認的ThreadFactory
 5     public static ThreadFactory defaultThreadFactory() {
 6         return new DefaultThreadFactory();//在前文中有該類的代碼
 7     }
 8     ......
 9     public static ExecutorService newCachedThreadPool() {
10         return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
11                                       60L, TimeUnit.SECONDS,
12                                       new SynchronousQueue<Runnable>());
13     }
14     ......
15     public static ExecutorService newFixedThreadPool(int nThreads) {
16         return new ThreadPoolExecutor(nThreads, nThreads,
17                                   0L, TimeUnit.MILLISECONDS,
18                                   new LinkedBlockingQueue<Runnable>());
19     }
20     ......
21     public static ExecutorService newSingleThreadExecutor() {
22         return new FinalizableDelegatedExecutorService
23             (new ThreadPoolExecutor(1, 1,
24                                 0L, TimeUnit.MILLISECONDS,
25                                 new LinkedBlockingQueue<Runnable>()));
26     }
27     ....
28 }
29 
30 public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService {
31     ....
32     public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
33     return new ScheduledThreadPoolExecutor(corePoolSize);
34     }
35     ....
36     public ScheduledThreadPoolExecutor(int corePoolSize) {
37     super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
38           new DelayedWorkQueue());
39     }
40     ....
41 }
42 
43 public class ThreadPoolExecutor extends AbstractExecutorService {
44     ......
45     public ThreadPoolExecutor(int corePoolSize,
46                           int maximumPoolSize,
47                           long keepAliveTime,
48                           TimeUnit unit,
49                           BlockingQueue<Runnable> workQueue) {
50         this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
51          Executors.defaultThreadFactory(), defaultHandler);
52     }
53     ......
54 }

1、CachedThreadPool線程池

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

       通過Executors的newCachedThreadPool方法來創建。它是一種線程數量不定的線程池它只有非核心線程,並且其最大線程數爲IntegerMAX_VALUE。由於IntegerMAX VALUE是一個很大的數,實際上就相當於最大線程數可以任意大。當線程池中的線程都處於活動狀態時,線程池會創建新的線程來處理新任務,否則就會利用空閒的線程來處理新任務。線程池中的空閒線程都有超時機制,這個超時時長爲60秒,超過60秒閒置線程就會被回收。和FixedThreadPool不同的是,CachedThreadPool的任務隊列其實相當於一個空集合。這將導致任何任務都會立即被執行,因爲在這種場景下SynchronousQueue是無法插入任務的。SynchronousQueue是一個非常特殊的隊列,在很多情況下可以把它簡單理解爲一個無法存儲元素的隊列,由於它在實際中較少使用,這裏就不深入探討它了。從 CachedThreadPool的特性來看,這類線程池比較適合執行大量的耗時較少的任務。當整個線程池都處於閒置狀態時,線程池中的線程都會超時而被停止,這個時候CachedThreadPool之中實際上是沒有任何線程的,它幾乎是不佔用任何系統資源的。

測試示例:

 1 public static void testNewCachedThreadPool() throws InterruptedException {
 2     ExecutorService executorService = Executors.newCachedThreadPool();
 3     for (int i = 1; i < 10; i++) {
 4         Thread.sleep(10);
 5         final int finalI = i;
 6         executorService.execute(new Runnable() {
 7             @Override
 8             public void run() {
 9                 System.out.println(("線程名稱:" + Thread.currentThread().getName() + ",執行" + finalI));
10             }
11         });
12     }
13 }

輸出結果:

線程名稱:pool-1-thread-1,執行1
線程名稱:pool-1-thread-1,執行2
線程名稱:pool-1-thread-1,執行3
線程名稱:pool-1-thread-1,執行4
線程名稱:pool-1-thread-1,執行5
線程名稱:pool-1-thread-1,執行6
線程名稱:pool-1-thread-1,執行7
線程名稱:pool-1-thread-1,執行8
線程名稱:pool-1-thread-1,執行9
第一次提交任務後,創建了pool-1-thread-1線程,執行完任務後有60s的空閒期,執行每一個任務時間非常短,所以只創建了一個線程且所有任務均由其執行。

2、FixedThreadPool線程池

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

       通過Executors的newFixedThreadPool方法來創建。它是一種線程數量固定的線程池,當線程處於空閒狀態時,它們並不會被回收,除非線程池被關閉了。當所有的線程都處於活動狀態時,新任務都會處於等待狀態,直到有線程空閒出來。由於FixedThreadPool只有核心線程並且這些核心線程不會被回收,這意味着它能夠更加快速地響應外界的請求。 newFixedThreadPool方法的實現如上,可以發現FixedThreadPool中只有核心線程並且這些核心線程沒有超時機制,另外任務隊列也是沒有大小限制的。

測試示例:

 1 public static void testNewFixedThreadPool() {
 2     ExecutorService executorService = Executors.newFixedThreadPool(3);
 3     for (int i = 0; i < 10; i++) {
 4         final int finalI = i;
 5         executorService.execute(new Runnable() {
 6             @Override
 7             public void run() {
 8                 try {
 9                     Thread.sleep(2000);
10                 } catch (InterruptedException e) {
11                     e.printStackTrace();
12                 }
13                 System.out.println(("線程名稱:" + Thread.currentThread().getName() + ",執行" + finalI));
14             }
15         });
16     }
17 }

輸出結果:

線程名稱:pool-1-thread-3,執行2
線程名稱:pool-1-thread-1,執行0
線程名稱:pool-1-thread-2,執行1
線程名稱:pool-1-thread-2,執行5
線程名稱:pool-1-thread-1,執行4
線程名稱:pool-1-thread-3,執行3
線程名稱:pool-1-thread-3,執行8
線程名稱:pool-1-thread-2,執行6
線程名稱:pool-1-thread-1,執行7
線程名稱:pool-1-thread-3,執行9

配置了3個核心線程,只有3個線程在執行任務。

3、SingleThreadExecutor線程池

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

       通過Executors的newSingleThreadExecutor方法來創建。這類線程池內部只有一個核心線程,它確保所有的任務都在同一個線程中按順序執行。SingleThreadExecutor的意義在於統一所有的外界任務到一個線程中這使得在這些任務之間不需要處理線程同步的問題。

測試示例:

 1 public static void testNewSingleThreadExecutor() {
 2     ExecutorService executorService = Executors.newSingleThreadExecutor();
 3     for (int i = 1; i < 10; i++) {
 4         final int finalI = i;
 5         executorService.execute(new Runnable() {
 6             @Override
 7             public void run() {
 8                 try {
 9                     Thread.sleep(2000);
10                 } catch (InterruptedException e) {
11                     e.printStackTrace();
12                 }
13                 System.out.println(("線程名稱:" + Thread.currentThread().getName() + ",執行" + finalI));
14             }
15         });
16     }
17 }

輸出結果:

線程名稱:pool-1-thread-1,執行1
線程名稱:pool-1-thread-1,執行2
線程名稱:pool-1-thread-1,執行3
線程名稱:pool-1-thread-1,執行4
線程名稱:pool-1-thread-1,執行5
線程名稱:pool-1-thread-1,執行6
線程名稱:pool-1-thread-1,執行7
線程名稱:pool-1-thread-1,執行8
線程名稱:pool-1-thread-1,執行9

儘管每個任務都執行時間都超過了2s,但始終只有1個線程在執行。

4、ScheduledThreadPool線程池

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

//ScheduledThreadPoolExecutor是ThreadPoolExecutor的子類
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

      通過Executors的newSingleThreadExecutor方法來創建。這類線程池內部只有一個核心線程,它確保所有的任務都在同一個線程中按順序執行。SingleThreadExecutor的意義在於統一所有的外界任務到一個線程中,這使得在這些任務之間不需要處理線程同步的問題。

測試示例:

 1 private static void testNewScheduledThreadPool() {
 2     ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
 3     executorService.schedule(new Runnable() {
 4         @Override
 5         public void run() {
 6             System.out.println("線程名稱:" + Thread.currentThread().getName() + ",執行:3秒後執行");
 7         }
 8     }, 3, TimeUnit.SECONDS);
 9     executorService.scheduleAtFixedRate(new Runnable() {
10         @Override
11         public void run() {
12             System.out.println("線程名稱:" + Thread.currentThread().getName() + ",執行:延遲2秒後每3秒執行一次");
13         }
14     }, 2, 3, TimeUnit.SECONDS);
15     for (int i = 0; i < 5; i++) {
16         final int finalI = i;
17         executorService.execute(new Runnable() {
18             @Override
19             public void run() {
20                 System.out.println("線程名稱:" + Thread.currentThread().getName() + ",執行:普通任務-" + finalI);
21             }
22         });
23     }
24 }

輸出結果:

線程名稱:pool-1-thread-1,執行:普通任務-0
線程名稱:pool-1-thread-2,執行:普通任務-1
線程名稱:pool-1-thread-3,執行:普通任務-2
線程名稱:pool-1-thread-4,執行:普通任務-3
線程名稱:pool-1-thread-5,執行:普通任務-4
線程名稱:pool-1-thread-2,執行:延遲2秒後每3秒執行一次
線程名稱:pool-1-thread-1,執行:3秒後執行
線程名稱:pool-1-thread-4,執行:延遲2秒後每3秒執行一次
線程名稱:pool-1-thread-4,執行:延遲2秒後每3秒執行一次
線程名稱:pool-1-thread-4,執行:延遲2秒後每3秒執行一次
線程名稱:pool-1-thread-4,執行:延遲2秒後每3秒執行一次
線程名稱:pool-1-thread-4,執行:延遲2秒後每3秒執行一次
線程名稱:pool-1-thread-4,執行:延遲2秒後每3秒執行一次

定時和延遲執行。

 

九、線程池在AsyncTask中的使用

       AsyncTask執行異步任務就是一個很典型的線程池使用範例,這裏我們來看看其中是如何使用線程池的。

  1、線程池參數的配置

 1 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
 2 // We want at least 2 threads and at most 4 threads in the core pool,
 3 // preferring to have 1 less than the CPU count to avoid saturating
 4 // the CPU with background work
 5 private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
 6 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
 7 private static final int KEEP_ALIVE_SECONDS = 30;
 8 private static final ThreadFactory sThreadFactory = new ThreadFactory() {
 9     private final AtomicInteger mCount = new AtomicInteger(1);
10     public Thread newThread(Runnable r) {
11         return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
12     }
13 };
14 private static final BlockingQueue<Runnable> sPoolWorkQueue =
15         new LinkedBlockingQueue<Runnable>(128);
16 
17 public static final Executor THREAD_POOL_EXECUTOR;
18 static {
19     ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
20             CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
21             sPoolWorkQueue, sThreadFactory);
22     threadPoolExecutor.allowCoreThreadTimeOut(true);
23     THREAD_POOL_EXECUTOR = threadPoolExecutor;
24 }
25 ......

  2、AsyncTask執行任務使用範例

一般使用AsyncTask執行任務的時候,使用方式如下:

new AsyncTask<Object, Object, Object>() {
    @Override
    protected Object doInBackground(Object[] objects) {
        return null;
    }
}.execute();

  3、AysncTask使用線程池執行任務源碼分析

 1 private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
 2 public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
 3 private static class SerialExecutor implements Executor {
 4     final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
 5     Runnable mActive;
 6     public synchronized void execute(final Runnable r) {
 7         mTasks.offer(new Runnable() {
 8             public void run() {
 9                 try {
10                     r.run();
11                 } finally {
12                     scheduleNext();
13                 }
14             }
15         });
16         if (mActive == null) {
17             scheduleNext();
18         }
19     }
20     protected synchronized void scheduleNext() {
21         if ((mActive = mTasks.poll()) != null) {
22             THREAD_POOL_EXECUTOR.execute(mActive);
23         }
24     }
25 }
26 
27 public final AsyncTask<Params, Progress, Result> execute(Params... params) {
28     return executeOnExecutor(sDefaultExecutor, params);
29 }
30 public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,Params... params) {
31     ......
32     exec.execute(mFuture);
33     return this;
34 }

第22行就是線程池調用了執行任務的方法。

本文知識點主要來源於任玉剛的《Android開發藝術探索》和某學課堂視頻教程的教案,並借鑑了https://www.cnblogs.com/zincredible/p/10984459.html的舉例。

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