併發編程系列之自定義線程池

前言

前面我們在講併發工具類的時候,多次提到線程池,今天我們就來走進線程池的旅地,首先我們先不講線程池框架Executors,我們今天先來介紹如何自己定義一個線程池,是不是已經迫不及待了,那麼就讓我們開啓今天的旅途吧。

 

什麼是線程池?

線程池可以理解爲一個專門管理線程生命週期的池子,裏面的線程都可以由這個池子本身來調度,使用線程池有哪些好處呢?

  • 降低資源消耗:通過重複利用已創建的線程降低線程創建和銷燬造成的消耗

  • 提高響應速度:當任務到達時,任務可以不需要的等到線程創建就能立即執行

  • 提高線程的可管理性:使用線程池可以對線程進行統一的分配,調優和監控

 

線程池的實現原理

首先我們看下面這張圖,對着圖進行分析:

過程分析:當任務到達時,首先會判斷核心線程池是否還有空閒線程,如果有則創建一個新的工作線程執行任務,如果沒有空閒線程,則說明核心線程池已滿,進行工作隊列是否滿的判斷,如果沒有滿,則將任務存放在等待隊列中,如果工作隊列也滿了,則再去判斷線程池是否滿,如果沒有滿,則新建一個線程來執行任務,否則採取拒絕策略;

下面我們來對核心線程池提交任務過程進行分析,這是線程池的核心角色,首先我們看下源碼:

public void execute(Runnable command) {
       if (command == null)
           throw new NullPointerException();
       int c = ctl.get();
       // 如果工作線程數量小於核心線程數,則創建一個新的工作線程執行任務
       if (workerCountOf(c) < corePoolSize) {
           // 創建工作線程成功,則直接返回
           if (addWorker(command, true))
               return;
           c = ctl.get();
       }
       // 工作線程>核心線程數或者工作線程創建失敗時,將任務放入等待隊列中
       if (isRunning(c) && workQueue.offer(command)) {
           int recheck = ctl.get();
           if (! isRunning(recheck) && remove(command))
               reject(command);
           else if (workerCountOf(recheck) == 0)
               addWorker(null, false);
       }
       // 如果等待隊列滿了,並且工作線程數量大於線程池總數量,則採取拒絕策略
       else if (!addWorker(command, false))
           reject(command);
   }

我們會發現,當可以分配線程來執行任務時,我們總是新建一個工作線程Worker來執行任務,我們來了解下Worker特殊的地方,工作線程不僅會執行當前的任務,而且當前任務執行完畢之後,還會去等待隊列中獲取任務來執行,通過工作線程的源碼可以得知這一點:

final void runWorker(Worker w) {
       Thread wt = Thread.currentThread();
       Runnable task = w.firstTask;
       w.firstTask = null;
       w.unlock(); // allow interrupts
       boolean completedAbruptly = true;
       try {
           while (task != null || (task = getTask()) != null) {
               w.lock();
               if ((runStateAtLeast(ctl.get(), STOP) ||
                    (Thread.interrupted() &&
                     runStateAtLeast(ctl.get(), STOP))) &&
                   !wt.isInterrupted())
                   wt.interrupt();
               try {
                   beforeExecute(wt, task);
                   Throwable thrown = null;
                   try {
                       task.run();
                   } catch (RuntimeException x) {
                       thrown = x; throw x;
                   } catch (Error x) {
                       thrown = x; throw x;
                   } catch (Throwable x) {
                       thrown = x; throw new Error(x);
                   } finally {
                       afterExecute(task, thrown);
                   }
               } finally {
                   task = null;
                   w.completedTasks++;
                   w.unlock();
               }
           }
           completedAbruptly = false;
       } finally {
           processWorkerExit(w, completedAbruptly);
       }
   }

上面我們講到的工作隊列,我們之前學到過隊列有很多種,例如阻塞隊列和非阻塞隊列,有界隊列和無界隊列,那麼在我們線程池中當使用不同的工作工作隊列又會有什麼區別呢?

  • 使用有界隊列時:有新的任務需要執行時,如果線程池實際線程數小於corePoolSize,則優先創建線程,如果大於corePoolSize,則會將任務先加入到隊列,等待執行,如果隊列也滿了,則在總線程數不大於maximumPoolSize時,先創建新的線程,如果線程數大於了maximumPoolSize則執行拒絕策略,或者其他自己自定義的處理策略;

  • 使用無界隊列時:LinkedBlockingQueue,與有界隊列相比,除非系統資源被耗盡,否則無界隊列的任務隊列不存在任務入隊列失敗的情況,當有新任務到來,系統的線程數小於corePoolSize時,則新建線程執行任務,當線程數量達到corePoolSize值後,則線程不會繼續新建,如果此時還持續有任務進來,而沒有空閒的線程資源,則任務會進入隊列排隊等待,若任務創建和處理的速度差異很大,則無界隊列會保持快速增長,直到資源耗盡內存,任務會一直堆積,直到內存滿了,這種情況永遠不會有有界隊列中工作線程和線程池總數的比較過程;

 

創建線程池:自定義線程池也是通過ThreadPoolExecutor(線程池執行器)來實現,構造方法如下

public ThreadPoolExecutor(int corePoolSize, //核心線程數--線程池初始化創建的線程數量  
                 int maximumPoolSize, // 最大線程數,線程池中能創建的最大線程數
                 long keepAliveTime, // 線程空閒等待時間
                 TimeUnit unit, // 線程空閒等待時間的單位
                 BlockingQueue<Runnable> workQueue, // 存放待執行任務的等待隊列
                 RejectedExecutionHandler handler // 拒絕任務的處理策略) {
       this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
            Executors.defaultThreadFactory(), handler);
   }

特別說明,拒絕策略有如下幾種:

  • AbortPolicy策略:該策略直接拋出異常,阻止系統工作

  • CallerRunsPolicy策略:只要線程池未關閉,該策略直接在調用者線程中運行當前被丟棄的任務。顯然這樣不會真的丟棄任務,但是,調用者線程性能可能急劇下降

  • DiscardOledestPolicy策略:丟棄最老的一個請求任務,也就是丟棄一個即將被執行的任務,並嘗試再次提交當前任務

  • DiscardPolicy策略:不處理,直接丟棄掉

 

向線程池提交任務:execute方法在上面已經介紹過了,這裏就不重複介紹了

 

關閉線程池:關閉線程池有下面2個方法

// 將線程池的狀態設置成SHUTDOWN狀態,然後中斷所有沒有正在執行任務的線程
public void shutdown() {
       final ReentrantLock mainLock = this.mainLock;
       mainLock.lock();
       try {
           checkShutdownAccess();
           advanceRunState(SHUTDOWN);
           // 調用線程中斷方法
           interruptIdleWorkers();
           onShutdown(); // hook for ScheduledThreadPoolExecutor
       } finally {
           mainLock.unlock();
       }
       tryTerminate();
   }

 

// 遍歷線程池中的工作線程,然後逐個調用線程的interrupt方法來中斷線程,所以無法響應中斷的任務可能永遠無法終止。
// shutdownNow會首先將線程池的狀態設置成STOP,然後嘗試停止所有的正在執行或暫停任務的線程,
// 並返回等待執行任務的列表,如果任務不一定要執行完,可以使用此方法   
public List<Runnable> shutdownNow() {
       List<Runnable> tasks;
       final ReentrantLock mainLock = this.mainLock;
       mainLock.lock();
       try {
           checkShutdownAccess();
           advanceRunState(STOP);
           interruptWorkers();
           tasks = drainQueue();
       } finally {
           mainLock.unlock();
       }
       tryTerminate();
       return tasks;
   }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章