帶你走近線程池(一 ):線程池工作機制和原理

 

一、爲什麼需要線程池

隨着系統用戶的逐漸增多,爲了提高用戶響應,提供一個高併發,高可用的系統。java的線程池就可以解決很多問題。很多異步,併發的場景都可以用到線程池。

1.降低重複創建線程的開銷,統一管理線程。

2.提高響應速度,任務提交後,不需要等待線程創建的過程。

3.提高線程的可管理性。重複管理線程,避免創建大量的線程增加開銷。

 


 

二、線程池的處理流程

1.線程池構造參數


   private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
   private final BlockingQueue<Runnable> workQueue;
   private final ReentrantLock mainLock = new ReentrantLock();
   private final HashSet<Worker> workers = new HashSet<Worker>();
   private volatile long keepAliveTime;
   private volatile boolean allowCoreThreadTimeOut;
   private volatile int corePoolSize;
   private volatile int maximumPoolSize;
   private volatile RejectedExcutionHandler handler;
    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

核心運行的poolSize,也就是當超過這個範圍的時候,就需要將新的Thread放入到等待隊列中了
maximumPoolSize   線程池最大容量大小,當大於了這個值就會將Thread由一個丟棄處理機制來處理
keepAliveTime  默認都是0,當線程沒有任務處理後,保持多長時間
TimeUnit  時間單位,
ThreadFactory  是構造Thread的方法,你可以自己去包裝和傳遞,主要實現newThread方法即可
BlockingQueue 等待隊列,當達到corePoolSize的時候,就向該等待隊列放入線程信息(默認爲一個LinkedBlockingQueue),運行中的隊列屬性爲:workers,爲一個HashSet;內部被包裝了一層
RejectedExecutionHandler  參數maximumPoolSize達到後丟棄處理的方法,java提供了4種丟棄處理的方法,當然你也可以自己弄,主要是要實現接口:RejectedExecutionHandler中的方法

2.處理流程

 

主要步驟:當提交一個任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閒的基本線程能夠執行新任務也會創建線程,等到需要執行的任務數大於線程池基本大小時就不再創建。如果調用了線程池的prestartAllCoreThreads()方法,線程池會提前創建並啓動所有基本線程。然後將任務提交到隊列,如果隊列滿了,並且已創建的線程數小於最大線程數,則線程池會再創建新的線程執行任務。值得注意的是,如果使用了無界的任務隊列這個參數就沒什麼效果。當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy,表示無法處理新任務時拋出異常。


三、ThreadPoolExecutor

1.具體執行方法

有一個重要的變量:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ctl維護兩個概念上的參數:workCount和runState。

workCount表示有效的線程數量,

runState表示線程池的運行狀態。 

 public void execute(Runnable command) {
        //檢查command不能爲null
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        //如果當前線程小於corePoolSize
        if (workerCountOf(c) < corePoolSize) {
            //如果添加Worker線程成功,則返回
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //如果當前正在運行階段並且可以將線程入隊
        if (isRunning(c) && workQueue.offer(command)) {
            //再次檢查ctl狀態
            int recheck = ctl.get();
            //如果不在運行狀態了,那麼就從隊列中移除任務
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //如果在運行階段,但是Worker數量爲0,調用addWorker方法
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //如果不能入隊,且不能創建Worker,那麼reject
        else if (!addWorker(command, false))
            reject(command);
    }

以上是核心的執行任務的方法:

從註釋中可以看到execute方法分爲三步:
(1)如果小於corePoolSize的線程正在運行,那麼創建一個核心線程,並將任務作爲核心線程的第一個任務
(2)如果一個任務可以加入到隊列中,然後需要在此檢查是否需要新建一個線程(因爲可能存在一個線程在上次檢查完之後被回收了)或者因爲線程池停止了。所以需要再次檢查狀態,如果不在RUNNING狀態並且能夠成功移除任務的話,那麼調用reject方法,否則就調用addWorker(null,false)方法。
(3)如果任務不能放入隊列,會首先嚐試添加一個新線程(非核心線程)。如果失敗,則調用reject方法。

 

 

 

 

 

 

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