多線程學習筆記-深入理解ThreadPoolExecutor


  java多線程中,線程池的最上層接口是Executor,ExecutorService實現了Executor,是真正的管理線程池的接口,ThreadPoolExecutor間接繼承了ExecutorService,提供了多種具體的線程池實現,在日常開發中一般直接使用Executors工具類提供的幾種常用ThreadPoolExecutor,下面詳細介紹下ThreadPoolExecutor.

ThreadPoolExecutor基本參數

ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 1000L, 
TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(20),
new ThreadPoolExecutor.CallerRunsPolicy());

  corePoolSize:核心線程數的大小,也有說線程池最小線程數

  maximumPoolSize:線程池最大線程數

  keepAliveTime:當沒有任務執行時,線程能存活的最大時間,這里說的線程是指大於corePoolSize,小於maximumPoolSize的線程

  timeunit:keepAliveTime的時間單位

  workQueue:用來存放task的堵塞隊列,隊列的選擇和size的大小對線程池的運行有直接影響,默認有幾種實現,后面詳說.

  rejectHandler:拒絕策略,當隊列已滿並且線程數達到maximumPoolSize時,再有新任務進來時會執行拒絕策略,默認集中實現,后面詳說.

ThreadPool模型初始化

  ThreadPool線程池初始化時,不會創建corePoolSIze的線程,也就是說在沒有task進來的時候,線程池是空的,當有task進來的時候,開始創建線程,並且線程執行完task后不會銷毀,而是駐留內存,直至達到corePoolSize,那什么時候線程數會再度增加達到maxPoolSize呢,這就取決於存放task的queue的size了,如果task的數量一直不超過指定的size那么就不會創建新的線程出來,反之,則會創建新的線程去執行task,那么新建出來的線程執行完task也會一直駐留內存嗎?答案是不會,這時候就要看設置的keepAliveTime,如果在超過了這個時間后還是沒有task去使用這個線程,則線程銷毀,直至線程數等於corePoolSize.那么如果queue也滿了,線程數也達到maxPoolsize,這時候怎么辦呢,這時rejectHandler就會發揮作用,會根據我們指定的拒絕策略去處理這種場景.

Executors的幾個默認實現

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

  1.newSingleThreadExecutor:創建一個單線程的線程池.如果這個線程在執行霍城中因為異常結束,則會創建一個新的線程來代替它,這個線程池保證所有任務的執行順序是按照任務提交順序進行.

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

  2.newFixedThreadPool:創建一個固定大小的線程池.看源碼可知,該實現創建了一個corePoolSize等於maximumPoolSize的線程池.

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

  3.newCachedThreadPool:創建一個可緩存的線程池.看源碼可知corePoolSize=0,即當線程空閑時會被回收,當線程忙碌時又可以"源源不斷"的創建新線程來執行task.

  默認線程池實現中還有ScheduledThreadPoolExecutor,可以實現定時的一些功能,可用來代替Timer或者TimeTask.這里不再展開說.

線程池的堵塞隊列

  如何選擇線程池的堵塞隊列,取決我們的業務場景,即我們希望以一種什么樣的排隊策略來處理任務.排隊通常有3種策略,對應下面幾種queue.

  直接提交(SynchronousQueue):不排隊,這個隊列不會存儲task,會將調用方的task直接提交給線程,指定了SynchronousQueue的線程池通常會把maximumPoolSize配置的比較大,否則可能會導致沒有足夠的線程來執行task,而導致task無法放入queue而被丟棄或拒絕.

  有界隊列(ArrayBlockingQueue):通過指定ArrayBlockingQueue的size可以設置隊列的最大存儲個數,當超出這個個數時就會新建線程去執行task直至線程數達到maximumPoolSize,有界隊列size的設置和maximumPoolSize的設置息息相關.會影響CPU的使用率以及系統吞吐量.

  無界隊列(不設定size的LinkedBlockingQueue):當線程數達到corePoolSize的仍有task進來時,會源源不斷進隊列,由於無解,maximumPoolSize參數會失效,線程數最大只能達到corPoolSize.

線程池拒絕策略

  CallerRunsPolicy:使用調用方的線程來執行task,通常情況下調用方線程就是指我們所說的主線程,這樣的好處是不會丟棄task,但是缺點也很明顯,使用這種策略會堵塞主線程,進而拖慢主線程的整個調用時長.而基於此,該策略同時也減緩了新的task提交進來的速度(因為主線程本來只需要用來提交task就好了,現在直接去執行task,后面的task進來的速度就慢了).

  AbortPolicy:這個策略簡單粗暴,直接拋出異常,不跟你多BB,需要注意的是,這個是jdk的默認策略.

  DiscardPolicy:這個和AbortPolicy差不多,區別是不會拋出異常,直接丟棄.

  DiscardOldestPolicy:這也是一種丟棄策略,不過和上面的DiscardPolicy剛好相反,她丟棄的不是新進來的task而是在堵塞隊列中存在時間最久的那個task,即丟棄最早進入隊列並且還沒有被執行的task.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM