日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

线程池是如何重复利用空闲的线程来执行任务的?

發布時間:2025/3/11 编程问答 17 豆豆
生活随笔 收集整理的這篇文章主要介紹了 线程池是如何重复利用空闲的线程来执行任务的? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

來源:blog.csdn.net/anhenzhufeng/article/details/88870374

在Java開發中,經常需要創建線程去執行一些任務,實現起來也非常方便,但如果并發的線程數量很多,并且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程就會大大降低系統的效率,因為頻繁創建線程和銷毀線程需要時間。此時,我們很自然會想到使用線程池來解決這個問題。

使用線程池的好處:

降低資源消耗。java中所有的池化技術都有一個好處,就是通過復用池中的對象,降低系統資源消耗。設想一下如果我們有n多個子任務需要執行,如果我們為每個子任務都創建一個執行線程,而創建線程的過程是需要一定的系統消耗的,最后肯定會拖慢整個系統的處理速度。而通過線程池我們可以做到復用線程,任務有多個,但執行任務的線程可以通過線程池來復用,這樣減少了創建線程的開銷,系統資源利用率得到了提升。

降低管理線程的難度。多線程環境下對線程的管理是最容易出現問題的,而線程池通過框架為我們降低了管理線程的難度。我們不用再去擔心何時該銷毀線程,如何最大限度的避免多線程的資源競爭。這些事情線程池都幫我們代勞了。

提升任務處理速度。線程池中長期駐留了一定數量的活線程,當任務需要執行時,我們不必先去創建線程,線程池會自己選擇利用現有的活線程來處理任務。

很顯然,線程池一個很顯著的特征就是“長期駐留了一定數量的活線程”,避免了頻繁創建線程和銷毀線程的開銷,那么它是如何做到的呢?我們知道一個線程只要執行完了run()方法內的代碼,這個線程的使命就完成了,等待它的就是銷毀。既然這是個“活線程”,自然是不能很快就銷毀的。為了搞清楚這個“活線程”是如何工作的,下面通過追蹤源碼來看看能不能解開這個疑問。

學習過線程池都知道,可以通過工廠類Executors來創個多種類型的線程池,部分類型如下:

public?static?ExecutorService?newFixedThreadPool(int?var0)?{return?new?ThreadPoolExecutor(var0,?var0,?0L,?TimeUnit.MILLISECONDS,?new?LinkedBlockingQueue());}public?static?ExecutorService?newSingleThreadExecutor()?{return?new?Executors.FinalizableDelegatedExecutorService(new?ThreadPoolExecutor(1,?1,?0L,?TimeUnit.MILLISECONDS,?new?LinkedBlockingQueue()));}public?static?ExecutorService?newCachedThreadPool()?{return?new?ThreadPoolExecutor(0,?2147483647,?60L,?TimeUnit.SECONDS,?new?SynchronousQueue());}public?static?ScheduledExecutorService?newSingleThreadScheduledExecutor()?{return?new?Executors.DelegatedScheduledExecutorService(new?ScheduledThreadPoolExecutor(1));}public?static?ScheduledExecutorService?newScheduledThreadPool(int?var0)?{return?new?ScheduledThreadPoolExecutor(var0);}

無論哪種類型的線程池,最終都是直接或者間接通過ThreadPoolExecutor這個類來實現的。而ThreadPoolExecutor的有多個構造方法,最終都是調用含有7個參數的構造函數。

/***?Creates?a?new?{@code?ThreadPoolExecutor}?with?the?given?initial*?parameters.**?@param?corePoolSize?the?number?of?threads?to?keep?in?the?pool,?even*????????if?they?are?idle,?unless?{@code?allowCoreThreadTimeOut}?is?set*?@param?maximumPoolSize?the?maximum?number?of?threads?to?allow?in?the*????????pool*?@param?keepAliveTime?when?the?number?of?threads?is?greater?than*????????the?core,?this?is?the?maximum?time?that?excess?idle?threads*????????will?wait?for?new?tasks?before?terminating.*?@param?unit?the?time?unit?for?the?{@code?keepAliveTime}?argument*?@param?workQueue?the?queue?to?use?for?holding?tasks?before?they?are*????????executed.??This?queue?will?hold?only?the?{@code?Runnable}*????????tasks?submitted?by?the?{@code?execute}?method.*?@param?threadFactory?the?factory?to?use?when?the?executor*????????creates?a?new?thread*?@param?handler?the?handler?to?use?when?execution?is?blocked*????????because?the?thread?bounds?and?queue?capacities?are?reached*?@throws?IllegalArgumentException?if?one?of?the?following?holds:<br>*?????????{@code?corePoolSize?<?0}<br>*?????????{@code?keepAliveTime?<?0}<br>*?????????{@code?maximumPoolSize?<=?0}<br>*?????????{@code?maximumPoolSize?<?corePoolSize}*?@throws?NullPointerException?if?{@code?workQueue}*?????????or?{@code?threadFactory}?or?{@code?handler}?is?null*/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

顧名思義,其指代核心線程的數量。當提交一個任務到線程池時,線程池會創建一個核心線程來執行任務,即使其他空閑的核心線程能夠執行新任務也會創建新的核心線程,而等到需要執行的任務數大于線程池核心線程的數量時就不再創建,這里也可以理解為當核心線程的數量等于線程池允許的核心線程最大數量的時候,如果有新任務來,就不會創建新的核心線程。

如果你想要提前創建并啟動所有的核心線程,可以調用線程池的prestartAllCoreThreads()方法。

② maximumPoolSize

顧名思義,其指代線程池允許創建的最大線程數。如果隊列滿了,并且已創建的線程數小于最大線程數,則線程池會再創建新的線程執行任務。所以只有隊列滿了的時候,這個參數才有意義。因此當你使用了無界任務隊列的時候,這個參數就沒有效果了。

③ keepAliveTime

顧名思義,其指代線程活動保持時間,即當線程池的工作線程空閑后,保持存活的時間。所以,如果任務很多,并且每個任務執行的時間比較短,可以調大時間,提高線程的利用率,不然線程剛執行完一個任務,還沒來得及處理下一個任務,線程就被終止,而需要線程的時候又再次創建,剛創建完不久執行任務后,沒多少時間又終止,會導致資源浪費。

注意:這里指的是核心線程池以外的線程。還可以設置allowCoreThreadTimeout = true這樣就會讓核心線程池中的線程有了存活的時間。

④ TimeUnit

顧名思義,其指代線程活動保持時間的單位:可選的單位有天(DAYS)、小時(HOURS)、分鐘(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和納秒(NANOSECONDS,千分之一微秒)。

⑤ workQueue

顧名思義,其指代任務隊列:用來保存等待執行任務的阻塞隊列。

⑥ threadFactory

顧名思義,其指代創建線程的工廠:可以通過線程工廠給每個創建出來的線程設置更加有意義的名字。

⑦ RejectedExecutionHandler

顧名思義,其指代拒絕執行程序,可以理解為飽和策略:當隊列和線程池都滿了,說明線程池處于飽和狀態,那么必須采取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy,表示無法處理新任務時拋出異常。在JDK1.5中Java線程池框架提供了以下4種策略。

  • AbortPolicy:直接拋出異常RejectedExecutionException。

  • CallerRunsPolicy:只用調用者所在線程來運行任務,即由調用 execute方法的線程執行該任務。

  • DiscardOldestPolicy:丟棄隊列里最近的一個任務,并執行當前任務。

  • DiscardPolicy:不處理,丟棄掉,即丟棄且不拋出異常。

這7個參數共同決定了線程池執行一個任務的策略:

當一個任務被添加進線程池時:

  • 線程數量未達到 corePoolSize,則新建一個線程(核心線程)執行任務

  • 線程數量達到了 corePools,則將任務移入隊列等待

  • 隊列已滿,新建線程(非核心線程)執行任務

  • 隊列已滿,總線程數又達到了 maximumPoolSize,就會由上面那位星期天(RejectedExecutionHandler)拋出異常

  • 說白了就是先利用核心線程,核心線程用完,新來的就加入等待隊列,一旦隊列滿了,那么只能開始非核心線程來執行了。

    上面的策略,會在閱讀代碼的時候體現出來,并且在代碼中也能窺探出真正復用空閑線程的實現原理。

    接下來我們就從線程池執行任務的入口分析。

    一個線程池可以接受任務類型有Runnable和Callable,分別對應了execute和submit方法。目前我們只分析execute的執行過程。

    上源碼:

    public?void?execute(Runnable?command)?{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();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);//啟動非核心線程執行,注意這里任務是null,其實里面會去取任務隊列里的任務執行}else?if?(!addWorker(command,?false))//第三步:加入不了隊列(即隊列滿了),嘗試啟動非核心線程reject(command);//如果啟動不了非核心線程執行,說明到達了最大線程數量的限制,會使用第7個參數拋出異常}

    代碼并不多,主要分三個步驟,其中有兩個靜態方法經常被用到,主要用來判斷線程池的狀態和有效線程數量:

    // 獲取運行狀態

    • private static int runStateOf(int c) ? ? { return c & ~CAPACITY; }

    // 獲取活動線程數

    • private static int workerCountOf(int c) ?{ return c & CAPACITY; }

    總結一下,execute的執行邏輯就是:

    • 如果 當前活動線程數 < 指定的核心線程數,則創建并啟動一個線程來執行新提交的任務(此時新建的線程相當于核心線程);

    • 如果 當前活動線程數 >= 指定的核心線程數,且緩存隊列未滿,則將任務添加到緩存隊列中;

    • 如果 當前活動線程數 >= 指定的核心線程數,且緩存隊列已滿,則創建并啟動一個線程來執行新提交的任務(此時新建的線程相當于非核心線程);

    從代碼中我們也可以看出,即便當前活動的線程有空閑的,只要這個活動的線程數量小于設定的核心線程數,那么依舊會啟動一個新線程來執行任務。也就是說不會去復用任何線程。在execute方法里面我們沒有看到線程復用的影子,那么我們繼續來看看addWorker方法。

    private?boolean?addWorker(Runnable?firstTask,?boolean?core)?{retry:for?(;;)?{int?c?=?ctl.get();int?rs?=?runStateOf(c);//?Check?if?queue?empty?only?if?necessary.if?(rs?>=?SHUTDOWN?&&!?(rs?==?SHUTDOWN?&&firstTask?==?null?&&!?workQueue.isEmpty()))return?false;for?(;;)?{int?wc?=?workerCountOf(c);if?(wc?>=?CAPACITY?||wc?>=?(core???corePoolSize?:?maximumPoolSize))return?false;if?(compareAndIncrementWorkerCount(c))break?retry;c?=?ctl.get();??//?Re-read?ctlif?(runStateOf(c)?!=?rs)continue?retry;//?else?CAS?failed?due?to?workerCount?change;?retry?inner?loop}}//前面都是線程池狀態的判斷,暫時不理會,主要看下面兩個關鍵的地方boolean?workerStarted?=?false;boolean?workerAdded?=?false;Worker?w?=?null;try?{w?=?new?Worker(firstTask);?//?新建一個Worker對象,這個對象包含了待執行的任務,并且新建一個線程final?Thread?t?=?w.thread;if?(t?!=?null)?{final?ReentrantLock?mainLock?=?this.mainLock;mainLock.lock();try?{//?Recheck?while?holding?lock.//?Back?out?on?ThreadFactory?failure?or?if//?shut?down?before?lock?acquired.int?rs?=?runStateOf(ctl.get());if?(rs?<?SHUTDOWN?||(rs?==?SHUTDOWN?&&?firstTask?==?null))?{if?(t.isAlive())?//?precheck?that?t?is?startablethrow?new?IllegalThreadStateException();workers.add(w);int?s?=?workers.size();if?(s?>?largestPoolSize)largestPoolSize?=?s;workerAdded?=?true;}}?finally?{mainLock.unlock();}if?(workerAdded)?{t.start();?//?啟動剛創建的worker對象里面的thread執行workerStarted?=?true;}}}?finally?{if?(!?workerStarted)addWorkerFailed(w);}return?workerStarted;}

    方法雖然有點長,但是我們只考慮兩個關鍵的地方,先是創建一個worker對象,創建成功后,對線程池狀態判斷成功后,就去執行該worker對象的thread的啟動。也就是說在這個方法里面啟動了一個關聯到worker的線程,但是這個線程是如何執行我們傳進來的runnable任務的呢?接下來看看這個Worker對象到底做了什么。

    private?final?class?Workerextends?AbstractQueuedSynchronizerimplements?Runnable{/***?This?class?will?never?be?serialized,?but?we?provide?a*?serialVersionUID?to?suppress?a?javac?warning.*/private?static?final?long?serialVersionUID?=?6138294804551838833L;/**?Thread?this?worker?is?running?in.??Null?if?factory?fails.?*/final?Thread?thread;/**?Initial?task?to?run.??Possibly?null.?*/Runnable?firstTask;/**?Per-thread?task?counter?*/volatile?long?completedTasks;/***?Creates?with?given?first?task?and?thread?from?ThreadFactory.*?@param?firstTask?the?first?task?(null?if?none)*/Worker(Runnable?firstTask)?{setState(-1);?//?inhibit?interrupts?until?runWorkerthis.firstTask?=?firstTask;this.thread?=?getThreadFactory().newThread(this);}/**?Delegates?main?run?loop?to?outer?runWorker.?*/public?void?run()?{runWorker(this);}//?Lock?methods////?The?value?0?represents?the?unlocked?state.//?The?value?1?represents?the?locked?state.protected?boolean?isHeldExclusively()?{return?getState()?!=?0;}protected?boolean?tryAcquire(int?unused)?{if?(compareAndSetState(0,?1))?{setExclusiveOwnerThread(Thread.currentThread());return?true;}return?false;}protected?boolean?tryRelease(int?unused)?{setExclusiveOwnerThread(null);setState(0);return?true;}public?void?lock()????????{?acquire(1);?}public?boolean?tryLock()??{?return?tryAcquire(1);?}public?void?unlock()??????{?release(1);?}public?boolean?isLocked()?{?return?isHeldExclusively();?}void?interruptIfStarted()?{Thread?t;if?(getState()?>=?0?&&?(t?=?thread)?!=?null?&&?!t.isInterrupted())?{try?{t.interrupt();}?catch?(SecurityException?ignore)?{}}}}

    最重要的構造方法:

    Worker(Runnable?firstTask)?{?//?worker本身實現了Runnable接口setState(-1);?//?inhibit?interrupts?until?runWorkerthis.firstTask?=?firstTask;?//?持有外部傳進來的runnable任務//創建了一個thread對象,并把自身這個runnable對象給了thread,一旦該thread執行start方法,就會執行worker的run方法this.thread?=?getThreadFactory().newThread(this);?}

    在addWorker方法中執行的t.start會去執行worker的run方法:

    public?void?run()?{runWorker(this);}

    run方法又執行了ThreadPoolExecutor的runWorker方法,把當前worker對象傳入。

    final?void?runWorker(Worker?w)?{Thread?wt?=?Thread.currentThread();Runnable?task?=?w.firstTask;?//?取出worker的runnable任務w.firstTask?=?null;w.unlock();?//?allow?interruptsboolean?completedAbruptly?=?true;try?{//?循環不斷的判斷任務是否為空,當第一個判斷為false的時候,即task為null,這個task啥時候為null呢?//?要么w.firstTask為null,還記得我們在execute方法第二步的時候,執行addWorker的時候傳進來的runnable是null嗎?//?要么是執行了一遍while循環,在下面的finally中執行了task=null;//?或者執行第二個判斷,一旦不為空就會繼續執行循環里的代碼。while?(task?!=?null?||?(task?=?getTask())?!=?null)?{w.lock();//?If?pool?is?stopping,?ensure?thread?is?interrupted;//?if?not,?ensure?thread?is?not?interrupted.??This//?requires?a?recheck?in?second?case?to?deal?with//?shutdownNow?race?while?clearing?interruptif?((runStateAtLeast(ctl.get(),?STOP)?||(Thread.interrupted()?&&runStateAtLeast(ctl.get(),?STOP)))?&&!wt.isInterrupted())wt.interrupt();try?{beforeExecute(wt,?task);Throwable?thrown?=?null;try?{task.run();?//?任務不為空,就會執行任務的run方法,也就是runnable的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;?//?執行完成置null,繼續下一個循環w.completedTasks++;w.unlock();}}completedAbruptly?=?false;}?finally?{processWorkerExit(w,?completedAbruptly);}}

    方法比較長,歸納起來就三步:

    1,從worker中取出runnable(這個對象有可能是null,見注釋中的解釋);

    2,進入while循環判斷,判斷當前worker中的runnable,或者通過getTask得到的runnable是否為空,不為空的情況下,就執行run;

    3,執行完成把runnable任務置為null。

    假如我們不考慮此方法里面的while循環的第二個判斷,在我們的線程開啟的時候,順序執行了runWorker方法后,當前worker的run就執行完成了。

    既然執行完了那么這個線程也就沒用了,只有等待虛擬機銷毀了。那么回顧一下我們的目標:Java線程池中的線程是如何被重復利用的?好像并沒有重復利用啊,新建一個線程,執行一個任務,然后就結束了,銷毀了。沒什么特別的啊,難道有什么地方漏掉了,被忽略了?

    仔細回顧下該方法中的while循環的第二個判斷(task = getTask)!=null

    玄機就在getTask方法中。

    private?Runnable?getTask()?{boolean?timedOut?=?false;?//?Did?the?last?poll()?time?out?for?(;;)?{int?c?=?ctl.get();int?rs?=?runStateOf(c);//?Check?if?queue?empty?only?if?necessary.if?(rs?>=?SHUTDOWN?&&?(rs?>=?STOP?||?workQueue.isEmpty()))?{decrementWorkerCount();return?null;}int?wc?=?workerCountOf(c);// timed變量用于判斷是否需要進行超時控制。// allowCoreThreadTimeOut默認是false,也就是核心線程不允許進行超時;// wc > corePoolSize,表示當前線程池中的線程數量大于核心線程數量;//?對于超過核心線程數量的這些線程或者允許核心線程進行超時控制的時候,需要進行超時控制//?Are?workers?subject?to?culling?boolean?timed?=?allowCoreThreadTimeOut?||?wc?>?corePoolSize;//?如果需要進行超時控制,且上次從緩存隊列中獲取任務時發生了超時(timedOut開始為false,后面的循環末尾超時時會置為true)//?或者當前線程數量已經超過了最大線程數量,那么嘗試將workerCount減1,即當前活動線程數減1,if?((wc?>?maximumPoolSize?||?(timed?&&?timedOut))&&?(wc?>?1?||?workQueue.isEmpty()))?{//?如果減1成功,則返回null,這就意味著runWorker()方法中的while循環會被退出,其對應的線程就要銷毀了,也就是線程池中少了一個線程了if?(compareAndDecrementWorkerCount(c))return?null;continue;}try?{//?注意workQueue中的poll()方法與take()方法的區別//poll方式取任務的特點是從緩存隊列中取任務,最長等待keepAliveTime的時長,取不到返回null//take方式取任務的特點是從緩存隊列中取任務,若隊列為空,則進入阻塞狀態,直到能取出對象為止Runnable?r?=?timed??workQueue.poll(keepAliveTime,?TimeUnit.NANOSECONDS)?:workQueue.take();if?(r?!=?null)return?r;timedOut?=?true;?//?能走到這里說明已經超時了}?catch?(InterruptedException?retry)?{timedOut?=?false;}}}

    注釋已經很清楚了,getTask的作用就是,在當前線程中:

    1,如果當前線程池線程數量大于核心線程數量或者設置了對核心線程進行超時控制的話(此時相當于對所有線程進行超時控制),就會去任務隊列獲取超時時間內的任務(隊列的poll方法),獲取到的話就會繼續執行任務,也就是執行runWorker方法中的while循環里的任務的run方法,執行完成后,又繼續進入getTask從任務隊列中獲取下一個任務。如果在超時時間內沒有獲取到任務,就會走到getTask的倒數第三行,設置timeOut標記為true,此時繼續進入getTask的for循環中,由于超時了,那么就會進入嘗試去去對線程數量-1操作,-1成功了,就直接返回一個null的任務,這樣就回到了當前線程執行的runWorker方法中,該方法的while循環判斷getTask為空,直接退出循環,這樣當前線程就執行完成了,意味著要被銷毀了,這樣自然就會被回收器擇時回收了。也就是線程池中少了一個線程了。因此只要線程池中的線程數大于核心線程數(或者核心線程也允許超時)就會這樣一個一個地銷毀這些多余的線程。

    2,如果當前活動線程數小于等于核心線程數(或者不允許核心線程超時),同樣也是去緩存隊列中取任務,但當緩存隊列中沒任務了,就會進入阻塞狀態(隊列的take方法),直到能取出任務為止(也就是隊列中被新添加了任務時),因此這個線程是處于阻塞狀態的,并不會因為緩存隊列中沒有任務了而被銷毀。這樣就保證了線程池有N個線程是活的,可以隨時處理任務,從而達到重復利用的目的。

    綜上所述,線程之所以能達到復用,就是在當前線程執行的runWorker方法中有個while循環,while循環的第一個判斷條件是執行當前線程關聯的Worker對象中的任務,執行一輪后進入while循環的第二個判斷條件getTask(),從任務隊列中取任務,取這個任務的過程要么是一直阻塞的,要么是阻塞一定時間直到超時才結束的,超時到了的時候這個線程也就走到了生命的盡頭。

    然而在我們開始分析execute的時候,這個方法中的三個部分都會調用addWorker去執行任務,在addWorker方法中都會去新建一個線程來執行任務,這樣的話是不是每次execute都是去創建線程了?事實上,復用機制跟線程池的阻塞隊列有很大關系,我們可以看到,在execute在核心線程滿了,但是隊列不滿的時候會把任務加入到隊列中,一旦加入成功,之前被阻塞的線程就會被喚醒去執行新的任務,這樣就不會重新創建線程了。

    我們用個例子來看下:

    假設我們有這么一個ThreadPoolExecutor,核心線程數設置為5(不允許核心線程超時),最大線程數設置為10,超時時間為20s,線程隊列是LinkedBlockingDeque(相當于是個無界隊列)。

    當我們給這個線程池陸續添加任務,前5個任務執行的時候,會執行到我們之前分析的execute方法的第一步部分,會陸續創建5個線程做為核心線程執行任務,當前線程里面的5個關聯的任務執行完成后,會進入各自的while循環的第二個判斷getTask中去取隊列中的任務,假設當前沒有新的任務過來也就是沒有執行execute方法,那么這5個線程就會在workQueue.take()處一直阻塞的。這個時候,我們執行execute加入一個任務,即第6個任務,這個時候會進入execute的第二部分,將任務加入到隊列中,一旦加入隊列,之前阻塞的5個線程其中一個就會被喚醒取出新加入的任務執行了。(這里有個execute的第二部分的后半段執行重復校驗的代碼即addWorker(傳入null任務),目前還沒搞明白是怎么回事)。

    在我們這個例子中,由于隊列是無界的,所以始終不會執行到execute的第三部分即啟動非核心線程,假如我們設置隊列為有界的,那么必然就會執行到這里了。

    小結

    通過以上的分析,應該算是比較清楚地解答了“線程池中的核心線程是如何被重復利用的”這個問題,同時也對線程池的實現機制有了更進一步的理解:

    當有新任務來的時候,先看看當前的線程數有沒有超過核心線程數,如果沒超過就直接新建一個線程來執行新的任務,如果超過了就看看緩存隊列有沒有滿,沒滿就將新任務放進緩存隊列中,滿了就新建一個線程來執行新的任務,如果線程池中的線程數已經達到了指定的最大線程數了,那就根據相應的策略拒絕任務。

    當緩存隊列中的任務都執行完了的時候,線程池中的線程數如果大于核心線程數,就銷毀多出來的線程,直到線程池中的線程數等于核心線程數。此時這些線程就不會被銷毀了,它們一直處于阻塞狀態,等待新的任務到來。

    注意:本文所說的“核心線程”、“非核心線程”是一個虛擬的概念,是為了方便描述而虛擬出來的概念,在代碼中并沒有哪個線程被標記為“核心線程”或“非核心線程”,所有線程都是一樣的,只是當線程池中的線程多于指定的核心線程數量時,會將多出來的線程銷毀掉,池中只保留指定個數的線程。那些被銷毀的線程是隨機的,可能是第一個創建的線程,也可能是最后一個創建的線程,或其它時候創建的線程。一開始我以為會有一些線程被標記為“核心線程”,而其它的則是“非核心線程”,在銷毀多余線程的時候只銷毀那些“非核心線程”,而“核心線程”不被銷毀。這種理解是錯誤的。

    往期推薦

    ThreadLocal內存溢出代碼演示和原因分析!


    ThreadLocal不好用?那是你沒用對!


    額!Java中用戶線程和守護線程區別這么大?



    創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的线程池是如何重复利用空闲的线程来执行任务的?的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。