Android執行緒池

李納斯小盒發表於2018-05-21

Android知識筆記 onGithub

[TOC]

因為建立和銷燬執行緒是需要代價的,所以當任務需要頻繁開啟執行緒的時候就需要使用執行緒池去管理。

1.執行緒池的建立

比較常見的是

public static final Executor THREAD_POOL_EXECUTOR        
    = new ThreadPoolExecutor(
            int corePoolSize,                          
            int maximumPoolSize,                          
            long keepAliveTime,                          
            TimeUnit unit,                          
            BlockingQueue<Runnable> workQueue,                          
            ThreadFactory threadFactory
複製程式碼

這個THREAD_POOL_EXECUTOR就是一個執行緒池,執行緒池都實現了ExecutorService的介面,定義了execute,shutdown,submit等方法。 下面解釋一下引數的含義。

  • corePoolSize corePoolSize表示核心執行緒數,corePoolSize數量的執行緒會一直存活,除非設定了allowCoreThreadTimeOut

  • maximumPoolSize maximumPoolSize是最大執行緒數量,maximumPoolSize一定是大於corePoolSize的,否則會報錯。噹噹前執行緒數量大於corePoolSize小於maximumPoolSize,並且阻塞佇列滿了,則建立新執行緒。

  • keepAliveTime keepAliveTime代表最大執行緒數量內,核心執行緒外的執行緒在空閒多久內不會被銷燬,也就是超過這個時間就會銷燬。

  • unit TimeUnit.SECONDS代表keepAliveTime的時間單位。

  • workQueue 在任務執行前儲存任務的佇列,噹噹前執行緒池執行緒數量達到corePoolSize並且當前所有執行緒都處於活動狀態,則execute提交的runnable儲存到佇列中。

  • threadFactory 當executor建立新執行緒的時候的工廠方法 例如:

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());    
        }
};
複製程式碼

還有一種:

public static final Executor THREAD_POOL_EXECUTOR        
    = new ThreadPoolExecutor(
            int corePoolSize,                          
            int maximumPoolSize,                          
            long keepAliveTime,                          
            TimeUnit unit,                          
            BlockingQueue<Runnable> workQueue,                          
            ThreadFactory threadFactory,
            RejectedExcutionHandler handler
複製程式碼

這個handlerworkQueue滿了之後對新加任務的處理策略,RejectedExcutionHandler是一個介面,包含 void rejectedExecution(Runnable r, ThreadPoolExecutor executor);方法,後面會講到。

2.封裝的執行緒池

不過,Java已經為我們封裝了一些執行緒池,在Executors中包含很多靜態方法。

  • 1.newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {    
        return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
複製程式碼

這個方法建立了一個數量固定的執行緒池,並且工作佇列是一個無限制大小的LinkedBlockingQueue。最大執行緒數量和核心執行緒數量相同。

  • 2.newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {    
    return new FinalizableDelegatedExecutorService(
          new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
 }
複製程式碼

newSingleThreadExecutor建立一個單執行緒,無限制的佇列。所以佇列的任務都是以順序的方式執行。

  • 3.newCachedThreadPool

適合執行大量短時間非同步任務的執行緒池。

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

建立一個核心執行緒數量0,最大執行緒數量為Integer.MAX_VALUE的執行緒池,執行緒存活時間60s,任務佇列為SynchronousQueue。

關於SynchronousQueue

SynchronousQueue是一個阻塞佇列,內部沒有容量,也就是capacity是0,每一個insert操作必須等另一個執行緒的remove操作。對SynchronousQueue使用peek操作都會返回null,也不能使用迭代器iterate。

  • 4.newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {    
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
複製程式碼

最終都會呼叫到

public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {    
      super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS, new DelayedWorkQueue(), threadFactory);}
複製程式碼

可以看到ScheduledThreadPoolExecutor是一個有固定數量的核心執行緒,最大執行緒數量是Integer.MAX_VALUE,如果執行緒執行完畢則會立即銷燬。任務佇列是DelayedWorkQueue。

通常ScheduledThreadPoolExecutor會定時執行任務,例如:

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
複製程式碼

initialDelay是第一次執行延長的時間,period是執行週期的次數。

DelayedWorkQueue

DelayedWorkQueue是ScheduledThreadPoolExecutor的內部類。 Java執行緒學習筆記(十一) DelayQueue的應用

3.任務佇列

  • ArrayBlockingQueue

基於陣列結構的有界佇列,按FIFO規則排序。佇列滿的話,還有任務想進入佇列,則呼叫拒絕策略。

  • LinkedBlockingQueue

基於連結串列的無界佇列,按FIFO排序,由於是無界的,所以採用此佇列後執行緒池將忽略拒絕策略引數,還會忽略最大執行緒數的引數。

  • SynchronousQueue

SynchronousQueue內部沒有快取,每一個 insert操作必須對應於另外一個執行緒的remove操作,反之亦然。你不能插入一個元素,除非有另外一個執行緒嘗試著remove。 其實SynchronousQueue的思想就是傳遞的性質,有人取得時候我才能加,僅此而已。所以SynchronousQueue 執行put()的時候不會返回,除非有對應的take(), 但是像大小是1的LinkedBlockingQueue,put() 操作會立即返回。SynchronousQueue內部使用了大量的ReentrantLock。

在stackoverflow上的回答有助於理解。

  • PriorityBlockingQueue

具有優先順序佇列的有界佇列,可自定義優先順序。

4.拒絕策略RejectedExcutionHandler

當執行緒池的任務快取佇列已滿並且執行緒池中的執行緒數目達到maximumPoolSize,如果還有任務到來就會採取任務拒絕策略。 AbortPolicyCallerRunsPolicyDiscardOldestPolicyDiscardPolicy都是在ThreadPoolExecutor中宣告的。

  • AbortPolicy

拒絕任務,丟擲RejectedExecutionException異常。

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
      throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());}
複製程式碼
  • CallerRunsPolicy

拒絕新任務進入,如果執行緒池沒有關閉,那麼新任務執行在呼叫executor的執行緒中直接執行。

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {        
                r.run();    
        }
}
複製程式碼
  • DiscardOldestPolicy 當執行緒池沒有關閉時,拋棄最老的還沒有執行的請求,然後執行execute
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {   
       if (!e.isShutdown()) {        
          e.getQueue().poll();        
          e.execute(r);    
        }
}
複製程式碼
  • DiscardPolicy

拋棄不能加入的任務,除此之外不做任何處理

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}
複製程式碼

舉例

    ThreadPoolExecutor pool = xxxxxx;  
    pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());  
複製程式碼

AsyncTask原始碼中的設定

在AsyncTask中,核心執行緒數量是CPU數量+1.

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
複製程式碼

最大執行緒數量是設定為2*CPU數量+1:

private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
複製程式碼

sPoolWorkQueue是任務的儲存佇列,在這裡是用一個大小128的連結串列去儲存的:

private static final BlockingQueue<Runnable> sPoolWorkQueue =        new LinkedBlockingQueue<Runnable>(128);
複製程式碼

相關文章