微信公衆號「後端進階」,專一後端技術分享:Java、Golang、WEB框架、分佈式中間件、服務治理等等。
老司機傾囊相授,帶你一路進階,來不及解釋了快上車!
多線程能夠說是面試官最喜歡拿來問的題目之一了,可謂是老生之常談,無論你是新手仍是老司機,我相信你必定會在面試過程當中遇到過有關多線程的一些問題。那我如今就充當一次面試官,我來問你:java
現有一個線程池,參數corePoolSize = 5,maximumPoolSize = 10,BlockingQueue阻塞隊列長度爲5,此時有4個任務同時進來,問:線程池會建立幾條線程?git
若是4個任務還沒處理完,這時又同時進來2個任務,問:線程池又會建立幾條線程仍是不會建立?github
若是前面6個任務仍是沒有處理完,這時又同時進來5個任務,問:線程池又會建立幾條線程仍是不會建立?面試
若是你此時一臉懵逼,請不要慌,問題不大。後端
要回答這個問題,咱們須要從建立線程池的參數去找答案:微信
java.util.concurrent.ThreadPoolExecutor#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.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
建立線程池一共有7個參數,從源碼可知,corePoolSize和maximumPoolSize都不能小於0,且核心線程數不能大於最大線程數。併發
下面我來解釋一下這7個參數的用途:框架
線程池核心線程數量,核心線程不會被回收,即便沒有任務執行,也會保持空閒狀態。若是線程池中的線程少於此數目,則在執行任務時建立。分佈式
池容許最大的線程數,當線程數量達到corePoolSize,且workQueue隊列塞滿任務了以後,繼續建立線程。
超過corePoolSize以後的「臨時線程」的存活時間。
keepAliveTime的單位。
當前線程數超過corePoolSize時,新的任務會處在等待狀態,並存在workQueue中,BlockingQueue是一個先進先出的阻塞式隊列實現,底層實現會涉及Java併發的AQS機制,有關於AQS的相關知識,我會單獨寫一篇,敬請期待。
建立線程的工廠類,一般咱們會自頂一個threadFactory設置線程的名稱,這樣咱們就能夠知道線程是由哪一個工廠類建立的,能夠快速定位。
線程池執行拒絕策略,當線數量達到maximumPoolSize大小,而且workQueue也已經塞滿了任務的狀況下,線程池會調用handler拒絕策略來處理請求。
系統默認的拒絕策略有如下幾種:
咱們還能夠自定義拒絕策略,只須要實現RejectedExecutionHandler接口便可,友好的拒絕策略實現有以下:
如今咱們回到剛開始的問題就很好回答了:
線程池corePoolSize=5,線程初始化時不會自動建立線程,因此當有4個任務同時進來時,執行execute方法會新建【4】條線程來執行任務;
前面的4個任務都沒完成,如今又進來2個隊列,會新建【1】條線程來執行任務,這時poolSize=corePoolSize,還剩下1個任務,線程池會將剩下這個任務塞進阻塞隊列中,等待空閒線程執行;
若是前面6個任務仍是沒有處理完,這時又同時進來了5個任務,此時尚未空閒線程來執行新來的任務,因此線程池繼續將這5個任務塞進阻塞隊列,但發現阻塞隊列已經滿了,核心線程也用完了,還剩下1個任務不知道如何是好,因而線程池只能建立【1】條「臨時」線程來執行這個任務了;
這裏建立的線程用「臨時」來描述仍是由於它們不會長期存在於線程池,它們的存活時間爲keepAliveTime,此後線程池會維持最少corePoolSize數量的線程。
JDK爲咱們提供了Executors線程池工具類,裏面有默認的線程池建立策略,大概有如下幾種:
用Executors工具類雖然很方便,我依然不推薦你們使用以上默認的線程池建立策略,阿里巴巴開發手冊也是強制不容許使用Executors來建立線程池,咱們從JDK源碼中尋找一波答案:
java.util.concurrent.Executors:
// FixedThreadPool public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } // SingleThreadPool public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } // CachedThreadPool public static ExecutorService newCachedThreadPool() { // 容許建立線程數爲Integer.MAX_VALUE return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } // ScheduledThreadPool public ScheduledThreadPoolExecutor(int corePoolSize) { // 容許建立線程數爲Integer.MAX_VALUE super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }
public LinkedBlockingQueue() { // 容許隊列長度最大爲Integer.MAX_VALUE this(Integer.MAX_VALUE); }
從JDK源碼可看出,Executors工具類無非是把一些特定參數進行了封裝,並提供一些方法供咱們調用而已,咱們並不能靈活地填寫參數,策略過於簡單,不夠友好。
CachedThreadPool和ScheduledThreadPool最大線程數爲Integer.MAX_VALUE,若是線程無限地建立,會形成OOM異常。
LinkedBlockingQueue基於鏈表的FIFO隊列,是無界的,默認大小是Integer.MAX_VALUE,所以FixedThreadPool和SingleThreadPool的阻塞隊列長度爲Integer.MAX_VALUE,若是此時隊列被無限地堆積任務,會形成OOM異常。