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

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

ThreadPoolExecutor基本參數

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

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

  maximumPoolSize:線程池最大線程數多線程

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

  timeunit:keepAliveTime的時間單位spa

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

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

ThreadPool模型初始化

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

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.

相關文章
相關標籤/搜索