java線程池的原理學習(二)

接上文:java線程池的原理學習java

ThreadPoolExecutor簡單介紹

ThreadPoolExecutor,線程池類,繼承自 AbstractExecutorServicesegmentfault

public class ThreadPoolExecutor extends AbstractExecutorService

構造方法

ThreadPoolExecutor 提供了四種構造方法實現(這裏只介紹一種):併發

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

有必要對每一個參數解釋一下:工具

  • corePoolSize - 池中所保存的線程數,包括空閒線程。學習

  • maximumPoolSize - 池中容許的最大線程數。this

  • keepAliveTime - 當線程數大於核心時,此爲終止前多餘的空閒線程等待新任務的最長時間。線程

  • unit - keepAliveTime 參數的時間單位。日誌

  • workQueue - 執行前用於保持任務的隊列。此隊列僅保持由 execute 方法提交的 Runnable 任務。code

  • threadFactory - 執行程序建立新線程時使用的工廠。orm

  • handler - 因爲超出線程範圍和隊列容量而使執行被阻塞時所使用的處理程序。

配置規則

爲了便於跨大量上下文使用,此類提供了不少可調整的參數和擴展鉤子 (hook)。jdk文檔中建議在一般狀況下,使用 Executors 提供的工廠方法配置,也就是提供好了的線程池。若非要手動配置,須要遵循如下規則:

核心和最大池大小

ThreadPoolExecutor 將根據 corePoolSizemaximumPoolSize 設置的邊界自動調整池大小。當新任務在方法 execute(java.lang.Runnable) 中提交時:
1.運行的線程少於 corePoolSize,則建立新線程來處理請求,即便其餘輔助線程是空閒的。
2:運行的線程多於 corePoolSize 而少於 maximumPoolSize,則把任務放進隊列,由空閒線程從隊列中取任務,僅當隊列滿時才建立新線程。
3:若是設置的 corePoolSizemaximumPoolSize 相同,則建立了固定大小的線程池。
4:若是將 maximumPoolSize 設置爲基本的無界值(如 Integer.MAX_VALUE ),則容許池適應任意數量的併發任務。

還要注意如下兩點:

  1. 在大多數狀況下,核心和最大池大小僅基於構造器來設置,不過也可使用 setCorePoolSize(int)setMaximumPoolSize(int) 進行動態更改。

  2. 當池中的線程數大於 corePoolSize 的時候,多餘的線程會等待 keepAliveTime 長的時間,若是無請求可處理就自行銷燬。

建立新線程

使用 ThreadFactory 建立新線程。若是沒有另外說明,則在同一個 ThreadGroup 中一概使用 Executors.defaultThreadFactory() 建立線程,而且這些線程具備相同的 NORM_PRIORITY 優先級和非守護進程狀態。經過提供不一樣的 ThreadFactory,能夠改變線程的名稱、線程組、優先級、守護進程狀態,等等。若是從 newThread 返回 null 時 ThreadFactory 未能建立線程,則執行程序將繼續運行,但不能執行任何任務。

ThreadFactory 是線程工廠,它是一個接口:

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

ThreadPoolExecutor 中的 threadFactory 是由 Executors 工具類提供的:

public static ThreadFactory defaultThreadFactory() {
        return new DefaultThreadFactory();
}
static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
           ////建立的線程以「pool-N-thread-M」命名,N是該工廠的序號,M是線程號
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0); 
            //設爲非後臺線程                      
            if (t.isDaemon())
                t.setDaemon(false);
            //優先級爲normal    
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

DefaultThreadFactory 是一個靜態內部類

排隊策略

前面說到,當線程池中運行的線程等於或多於 corePoolSize,則 Executor 始終首選將請求加入隊列,而不添加新的線程,將任務加入隊列有三種策略(具體參見jdk文檔)。

被拒絕的任務

兩種狀況下,新提交的任務將會被拒絕:

  1. Executor 已經關閉

  2. Executor 將有限邊界用於最大線程和工做隊列容量,且已經飽和

被拒絕的任務, execute 方法都將調用其 RejectedExecutionHandlerRejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。下面提供了四種預約義的處理程序策略:

在默認的 ThreadPoolExecutor.AbortPolicy 中,處理程序遭到拒絕將拋出運行時 RejectedExecutionException
ThreadPoolExecutor.CallerRunsPolicy 中,線程調用運行該任務的 execute 自己。此策略提供簡單的反饋控制機制,可以減緩新任務的提交速度。
ThreadPoolExecutor.DiscardPolicy 中,不能執行的任務將被刪除。
ThreadPoolExecutor.DiscardOldestPolicy 中,若是執行程序還沒有關閉,則位於工做隊列頭部的任務將被刪除,而後重試執行程序(若是再次失敗,則重複此過程)。

定義和使用其餘種類的 RejectedExecutionHandler 類也是可能的,但這樣作須要很是當心,尤爲是當策略僅用於特定容量或排隊策略時。

鉤子方法

此類提供兩個 protected 可重寫的 鉤子方法:

protected void beforeExecute(Thread t, Runnable r) { }
protected void afterExecute(Runnable r, Throwable t) { }

這兩種方法分別在執行 每一個任務 以前和以後調用。它們可用於操縱執行環境;注意這裏是每一個任務,即每次運行新任務時都會執行一遍。例如,從新初始化 ThreadLocal 、蒐集統計信息或添加日誌條目。此外,還能夠重寫方法 terminated() 來執行 Executor 徹底終止後須要完成的全部特殊處理。
若是鉤子 (hook) 或回調方法拋出異常,則內部輔助線程將依次失敗並忽然終止。

jdk文檔中提供了一個能夠暫停和恢復的線程池例子:

class PausableThreadPoolExecutor extends ThreadPoolExecutor {
   private boolean isPaused;
   private ReentrantLock pauseLock = new ReentrantLock();
   private Condition unpaused = pauseLock.newCondition();

   public PausableThreadPoolExecutor(...) { super(...); }
   protected void beforeExecute(Thread t, Runnable r) {
     super.beforeExecute(t, r);
     pauseLock.lock();
     try {
       while (isPaused) unpaused.await();
     } catch(InterruptedException ie) {
       t.interrupt();
     } finally {
       pauseLock.unlock();
     }
   }
   public void pause() {
     pauseLock.lock();
     try {
       isPaused = true;
     } finally {
       pauseLock.unlock();
     }
   }
   public void resume() {
     pauseLock.lock();
     try {
       isPaused = false;
       unpaused.signalAll();
     } finally {
       pauseLock.unlock();
     }
   }
 }

ThreadPoolExecutor運行原理

java線程池的原理學習(三)

相關文章
相關標籤/搜索