Android線程池的使用

前言

?多任務處理在現實開發場景中已經無處不在,通過多任務處理可以將計算機性能更大程度的發揮出來,避免處于空閑狀態浪費性能。
?對于計算量相同的任務,程序線程并發協調得越有條不紊,效率自然就會越高;反之,線程之間頻繁爭用數據,互相阻塞甚至死鎖,將會大大降低程序的并發能力。
?因此我們有必要深入了解多線程開發。

1 概念

在說線程之前我們先來了解進程、線程、線程池概念。

  • 進程(process)是指在系統中正在運行的一個應用程序。
  • 線程(Thread)是比進程更輕量級的調度執行單位,它可以把一個進程的資源分配和執行調度分開。目前線程是CPU調度執行的最基本單位。
  • 線程池(ThreadPool)自動創建、銷毀線程的一個容器。

2 線程

?不建議直接new線程,因為存在以下幾點問題:
①不易復用,頻繁創建及銷毀開銷大
②特定場景不易使用,例如定時任務
?一個簡單的線程實現方式

  fun doSomething() {
        Thread{
            //todo
        }.start()
    }

3 線程池

?線程池的優點:
①可重用線程池中的線程,避免頻繁創建及銷毀帶來的性能開銷。
②可控制最大并發數,避免大量線程之間因為相互搶占系統資源而導致阻塞。
③支持特定場景使用,例如定時任務。
?我們日常開發中所使用到的線程池在java.util.concurrent并發工具包下,繼承自Executor實現了ThreadPoolExecutor,ThreadPoolExecutor提供了一系列參數配置線程,如下所示:

/**
     *
     * @param corePoolSize 核心線程數,默認情況一直存活
     *      
     * @param maximumPoolSize 線程池最大線程數
     *        
     * @param keepAliveTime 非核心線程閑置時的超時時長,超過這個時長就會被           
     *  回收。當allowCoreThreadTimeOut設置為true時作用于核心線程 。
     *   
     * @param unit 指定keepAliveTime存活時常的單位,TimeUnit是個枚舉類,一般 
     * 有TimeUnit.SECONDS,TimeUnit.MINUTES
     *       
     * @param workQueue 線程池任務隊列,通過execut提交Runnable執行。
     *    
     * @param threadFactory 當創建新線程時可以使用該工廠創建
     *   
     * @throws 滿足以下條件拋出此異常   IllegalArgumentException 
     *         {corePoolSize < 0}
     *         {keepAliveTime < 0}
     *         {maximumPoolSize <= 0}
     *         {maximumPoolSize < corePoolSize}
     * @throws NullPointerException  workQueue、threadFactory為null時拋出異常。
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

?除以上參數外,還有一個參數RejectedExecutionHandler,當任務隊列已滿或者是無法成功執行任務,就會調用RejectedExecutionHandler的rejectedExecution,默認拋出異常。ThreadPoolExecutor還為我們提供了CallerRunsPolicyAbortPolicyDiscardPolicyDiscardOldestPolicy四種Handler,默認為AbortPolicy,因此是直接拋出異常。

    public static class AbortPolicy implements RejectedExecutionHandler {
        public AbortPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

?一個簡單的線程池工具類創建如下:

 private int CPU_Runtime = Runtime.getRuntime().availableProcessors() + 1;

 public ThreadPoolExecutor threadPoolExecutor;

 public ThreadPoolUtils() {
        threadPoolExecutor = new ThreadPoolExecutor(CPU_Runtime, CPU_Runtime, 
                1, TimeUnit.MINUTES,
                new LinkedBlockingQueue<Runnable>(128),
                new MyThreadFactory("test"));
    }

    /**
     * 記錄是線程池中第幾個線程
     */
    private static class MyThreadFactory implements ThreadFactory {

        private final String name;
        private final AtomicInteger mCount = new AtomicInteger(1);

        MyThreadFactory(String name) {
            this.name = name;
        }

        @Override
        public Thread newThread(@NonNull Runnable r) {
            return new Thread(r, name + "-" + mCount.getAndIncrement() + "-");
        }
    }

?根據實際開發需求做了以上這些配置,配置參數規格如下:

  • 核心線程數為CPU核心數+1
  • 非核心線程數與核心線程數相同
  • 非核心線程超時時間為1分鐘
  • 任務隊列容量為128
  • 使用工廠模式創建線程,便于查看當前執行的線程相關信息。

4 常用的4種線程池

?java還為我們實現了配置了4種常用的線程池,newFixedThreadPoolnewCachedThreadPoolnewScheduledThreadPoolnewSingleThreadExecutor,它們都直接或間接配置了ThreadPoolExecutor。

4.1 newFixedThreadPool

?固定線程數的線程池,當處于空閑狀態不會被回收。因為最大線程數和核心線程相等,所以都是核心線程,處于空閑狀態不會被回收。

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

4.2 newCachedThreadPool

?數量不固定的線程池,因為最大線程數為Integer.MAX_VALUE,但是一般不會創建太多,因為創建線程開銷比較消耗性能。默認超時時長為60S。

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

4.3 newScheduledThreadPool

?創建固定數量的核心線程以及Integer.MAX_VALUE非核心線程,默認超時時長為10分鐘,一般用于執行固定周期的重讀任務。

  private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
  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());
    }

4.4 newSingleThreadExecutor

?只有一個核心線程的線程池,它保證了所有的線程在此隊列中都是有序執行的。一般用于統一外界任務,避免處理同步問題。

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

總結

?java.util.concurrent為我們提供了線程池的創建,方便我們管理線程,但隨著變成語言的發展,協程也出現在了我們日常開發中,無論是Java即將面世的遷程(Fiber)或者kotlin的協程(Coroutines),它們的創建與銷毀的開銷遠遠小于線程的創建,因此在使用kotlin開發Android時建議多使用協程進行多并發開發。

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