自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

線程池是如何重復(fù)利用空閑線程的?

開發(fā) 后端
在Java開發(fā)中,經(jīng)常需要創(chuàng)建線程去執(zhí)行一些任務(wù),實現(xiàn)起來也非常方便,但如果并發(fā)的線程數(shù)量很多,并且每個線程都是執(zhí)行一個時間很短的任務(wù)就結(jié)束了,這樣頻繁創(chuàng)建線程就會大大降低系統(tǒng)的效率。

[[407093]]

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

使用線程池的好處:

降低資源消耗。

java中所有的池化技術(shù)都有一個好處,就是通過復(fù)用池中的對象,降低系統(tǒng)資源消耗。設(shè)想一下如果我們有n多個子任務(wù)需要執(zhí)行,如果我們?yōu)槊總€子任務(wù)都創(chuàng)建一個執(zhí)行線程,而創(chuàng)建線程的過程是需要一定的系統(tǒng)消耗的,最后肯定會拖慢整個系統(tǒng)的處理速度。而通過線程池我們可以做到復(fù)用線程,任務(wù)有多個,但執(zhí)行任務(wù)的線程可以通過線程池來復(fù)用,這樣減少了創(chuàng)建線程的開銷,系統(tǒng)資源利用率得到了提升。

降低管理線程的難度。

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

提升任務(wù)處理速度。

線程池中長期駐留了一定數(shù)量的活線程,當任務(wù)需要執(zhí)行時,我們不必先去創(chuàng)建線程,線程池會自己選擇利用現(xiàn)有的活線程來處理任務(wù)。

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

學(xué)習(xí)過線程池都知道,可以通過工廠類Executors來創(chuàng)個多種類型的線程池,部分類型如下: 

  1. public static ExecutorService newFixedThreadPool(int var0) {  
  2.     return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());  
  3.  
  4. public static ExecutorService newSingleThreadExecutor() {  
  5.     return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));  
  6.  
  7. public static ExecutorService newCachedThreadPool() {  
  8.     return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue()); 
  9.  
  10. public static ScheduledExecutorService newSingleThreadScheduledExecutor() {  
  11.     return new Executors.DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1));  
  12.  
  13. public static ScheduledExecutorService newScheduledThreadPool(int var0) {  
  14.     return new ScheduledThreadPoolExecutor(var0);  

無論哪種類型的線程池,最終都是直接或者間接通過ThreadPoolExecutor這個類來實現(xiàn)的。而ThreadPoolExecutor的有多個構(gòu)造方法,最終都是調(diào)用含有7個參數(shù)的構(gòu)造函數(shù)。 

  1. /**  
  2.  * Creates a new {@code ThreadPoolExecutor} with the given initial  
  3.  * parameters.  
  4.  *  
  5.  * @param corePoolSize the number of threads to keep in the pool, even  
  6.  *        if they are idle, unless {@code allowCoreThreadTimeOut} is set  
  7.  * @param maximumPoolSize the maximum number of threads to allow in the  
  8.  *        pool  
  9.  * @param keepAliveTime when the number of threads is greater than  
  10.  *        the core, this is the maximum time that excess idle threads  
  11.  *        will wait for new tasks before terminating.  
  12.  * @param unit the time unit for the {@code keepAliveTime} argument  
  13.  * @param workQueue the queue to use for holding tasks before they are  
  14.  *        executed.  This queue will hold only the {@code Runnable}  
  15.  *        tasks submitted by the {@code execute} method.  
  16.  * @param threadFactory the factory to use when the executor  
  17.  *        creates a new thread  
  18.  * @param handler the handler to use when execution is blocked  
  19.  *        because the thread bounds and queue capacities are reached  
  20.  * @throws IllegalArgumentException if one of the following holds:<br>  
  21.  *         {@code corePoolSize < 0}<br>  
  22.  *         {@code keepAliveTime < 0}<br>  
  23.  *         {@code maximumPoolSize <= 0}<br>  
  24.  *         {@code maximumPoolSize < corePoolSize 
  25.  * @throws NullPointerException if {@code workQueue}  
  26.  *         or {@code threadFactory} or {@code handler} is null  
  27.  */  
  28. public ThreadPoolExecutor(int corePoolSize,  
  29.                           int maximumPoolSize,  
  30.                           long keepAliveTime, 
  31.                            TimeUnit unit,  
  32.                           BlockingQueue<Runnable> workQueue,  
  33.                           ThreadFactory threadFactory,  
  34.                           RejectedExecutionHandler handler) {  
  35.     if (corePoolSize < 0 ||  
  36.         maximumPoolSize <= 0 ||  
  37.         maximumPoolSize < corePoolSize ||  
  38.         keepAliveTime < 0 
  39.         throw new IllegalArgumentException();  
  40.     if (workQueue == null || threadFactory == null || handler == null)  
  41.         throw new NullPointerException();  
  42.     this.corePoolSize = corePoolSize; 
  43.     this.maximumPoolSize = maximumPoolSize;  
  44.     this.workQueue = workQueue;  
  45.     this.keepAliveTime = unit.toNanos(keepAliveTime);  
  46.     this.threadFactory = threadFactory;  
  47.     this.handler = handler;  

① corePoolSize

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

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

② maximumPoolSize

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

③ keepAliveTime

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

注意:這里指的是核心線程池以外的線程。還可以設(shè)置allowCoreThreadTimeout = true這樣就會讓核心線程池中的線程有了存活的時間。另外,Java 多線程面試題都整理好了,微信搜索Java技術(shù)棧,在后臺發(fā)送:面試,即可在線閱讀全部答案。

④ TimeUnit

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

⑤ workQueue

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

⑥ threadFactory

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

⑦ RejectedExecutionHandler

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

AbortPolicy:直接拋出異常RejectedExecutionException。

CallerRunsPolicy:只用調(diào)用者所在線程來運行任務(wù),即由調(diào)用 execute方法的線程執(zhí)行該任務(wù)。

DiscardOldestPolicy:丟棄隊列里最近的一個任務(wù),并執(zhí)行當前任務(wù)。

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

這7個參數(shù)共同決定了線程池執(zhí)行一個任務(wù)的策略:

當一個任務(wù)被添加進線程池時:

  1.   線程數(shù)量未達到 corePoolSize,則新建一個線程(核心線程)執(zhí)行任務(wù)
  2.   線程數(shù)量達到了 corePools,則將任務(wù)移入隊列等待
  3.   隊列已滿,新建線程(非核心線程)執(zhí)行任務(wù)
  4.   隊列已滿,總線程數(shù)又達到了 maximumPoolSize,就會由上面那位星期天(RejectedExecutionHandler)拋出異常

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

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

接下來我們就從線程池執(zhí)行任務(wù)的入口分析。

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

上源碼: 

  1. public void execute(Runnable command) {  
  2.     if (command == null)  
  3.         throw new NullPointerException();  
  4.     /*  
  5.      * Proceed in 3 steps:  
  6.      * 
  7.      * 1. If fewer than corePoolSize threads are running, try to  
  8.      * start a new thread with the given command as its first  
  9.      * task.  The call to addWorker atomically checks runState and  
  10.      * workerCount, and so prevents false alarms that would add  
  11.      * threads when it shouldn't, by returning false.  
  12.      *  
  13.      * 2. If a task can be successfully queued, then we still need  
  14.      * to double-check whether we should have added a thread  
  15.      * (because existing ones died since last checking) or that  
  16.      * the pool shut down since entry into this method. So we  
  17.      * recheck state and if necessary roll back the enqueuing if  
  18.      * stopped, or start a new thread if there are none.  
  19.      *  
  20.      * 3. If we cannot queue task, then we try to add a new  
  21.      * thread.  If it fails, we know we are shut down or saturated  
  22.      * and so reject the task.  
  23.      */  
  24.     int c = ctl.get();  
  25.     if (workerCountOf(c) < corePoolSize) { //第一步:如果線程數(shù)量小于核心線程數(shù)  
  26.         if (addWorker(command, true))//則啟動一個核心線程執(zhí)行任務(wù)  
  27.             return;  
  28.         c = ctl.get();  
  29.     }  
  30.     if (isRunning(c) && workQueue.offer(command)) {//第二步:當前線程數(shù)量大于等于核心線程數(shù),加入任務(wù)隊列,成功的話會進行二次檢查  
  31.         int recheck = ctl.get();  
  32.         if (! isRunning(recheck) && remove(command))  
  33.             reject(command);  
  34.         else if (workerCountOf(recheck) == 0)  
  35.             addWorker(null, false);//啟動非核心線程執(zhí)行,注意這里任務(wù)是null,其實里面會去取任務(wù)隊列里的任務(wù)執(zhí)行  
  36.     }  
  37.     else if (!addWorker(command, false))//第三步:加入不了隊列(即隊列滿了),嘗試啟動非核心線程  
  38.         reject(command);//如果啟動不了非核心線程執(zhí)行,說明到達了最大線程數(shù)量的限制,會使用第7個參數(shù)拋出異常  

代碼并不多,主要分三個步驟,其中有兩個靜態(tài)方法經(jīng)常被用到,主要用來判斷線程池的狀態(tài)和有效線程數(shù)量: 

  1. // 獲取運行狀態(tài) 
  2.  
  3. private static int runStateOf(int c)     { return c & ~CAPACITY; }  
  4. // 獲取活動線程數(shù) 
  5. private static int workerCountOf(int c)  { return c & CAPACITY; } 

總結(jié)一下,execute的執(zhí)行邏輯就是:

  •   如果 當前活動線程數(shù) < 指定的核心線程數(shù),則創(chuàng)建并啟動一個線程來執(zhí)行新提交的任務(wù)(此時新建的線程相當于核心線程);
  •   如果 當前活動線程數(shù) >= 指定的核心線程數(shù),且緩存隊列未滿,則將任務(wù)添加到緩存隊列中;
  •   如果 當前活動線程數(shù) >= 指定的核心線程數(shù),且緩存隊列已滿,則創(chuàng)建并啟動一個線程來執(zhí)行新提交的任務(wù)(此時新建的線程相當于非核心線程);

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

另外,Java 核心技術(shù)教程教程示例源碼看這里:https://github.com/javastacks/javastack 

  1. private boolean addWorker(Runnable firstTask, boolean core) {  
  2.     retry:  
  3.     for (;;) {  
  4.         int c = ctl.get();  
  5.         int rs = runStateOf(c);  
  6.         // Check if queue empty only if necessary.  
  7.         if (rs >= SHUTDOWN &&  
  8.             ! (rs == SHUTDOWN &&  
  9.                firstTask == null &&  
  10.                ! workQueue.isEmpty()))  
  11.             return false;  
  12.         for (;;) {  
  13.             int wc = workerCountOf(c);  
  14.             if (wc >= CAPACITY ||  
  15.                 wc >= (core ? corePoolSize : maximumPoolSize))  
  16.                 return false;  
  17.             if (compareAndIncrementWorkerCount(c))  
  18.                 break retry;  
  19.             c = ctl.get();  // Re-read ctl  
  20.             if (runStateOf(c) != rs)  
  21.                 continue retry;  
  22.             // else CAS failed due to workerCount change; retry inner loop  
  23.         }  
  24.     }  
  25.     //前面都是線程池狀態(tài)的判斷,暫時不理會,主要看下面兩個關(guān)鍵的地方  
  26.     boolean workerStarted = false 
  27.     boolean workerAdded = false 
  28.     Worker w = null 
  29.     try { 
  30.          w = new Worker(firstTask); // 新建一個Worker對象,這個對象包含了待執(zhí)行的任務(wù),并且新建一個線程 
  31.         final Thread t = w.thread;  
  32.         if (t != null) {  
  33.             final ReentrantLock mainLock = this.mainLock;  
  34.             mainLock.lock();  
  35.             try { 
  36.                  // Recheck while holding lock. 
  37.                  // Back out on ThreadFactory failure or if  
  38.                 // shut down before lock acquired.  
  39.                 int rs = runStateOf(ctl.get());  
  40.                 if (rs < SHUTDOWN ||  
  41.                     (rs == SHUTDOWN && firstTask == null)) {  
  42.                     if (t.isAlive()) // precheck that t is startable  
  43.                         throw new IllegalThreadStateException();  
  44.                     workers.add(w); 
  45.                      int s = workers.size();  
  46.                     if (s > largestPoolSize)  
  47.                         largestPoolSize = s;  
  48.                     workerAdded = true
  49.                 }  
  50.             } finally {  
  51.                 mainLock.unlock();  
  52.             }  
  53.             if (workerAdded) {  
  54.                 t.start(); // 啟動剛創(chuàng)建的worker對象里面的thread執(zhí)行  
  55.                 workerStarted = true 
  56.             }  
  57.         }  
  58.     } finally { 
  59.         if (! workerStarted)  
  60.             addWorkerFailed(w);  
  61.     }  
  62.     return workerStarted;  

方法雖然有點長,但是我們只考慮兩個關(guān)鍵的地方,先是創(chuàng)建一個worker對象,創(chuàng)建成功后,對線程池狀態(tài)判斷成功后,就去執(zhí)行該worker對象的thread的啟動。也就是說在這個方法里面啟動了一個關(guān)聯(lián)到worker的線程,但是這個線程是如何執(zhí)行我們傳進來的runnable任務(wù)的呢?

接下來看看這個Worker對象到底做了什么。 

  1. private final class Worker  
  2.     extends AbstractQueuedSynchronizer  
  3.     implements Runnable  
  4.  
  5.     /**  
  6.      * This class will never be serialized, but we provide a  
  7.      * serialVersionUID to suppress a javac warning.  
  8.      */  
  9.     private static final long serialVersionUID = 6138294804551838833L  
  10.     /** Thread this worker is running in.  Null if factory fails. */  
  11.     final Thread thread;  
  12.     /** Initial task to run.  Possibly null. */ 
  13.     Runnable firstTask;  
  14.     /** Per-thread task counter */  
  15.     volatile long completedTasks;  
  16.     /** 
  17.      * Creates with given first task and thread from ThreadFactory.  
  18.      * @param firstTask the first task (null if none)  
  19.      */  
  20.     Worker(Runnable firstTask) {  
  21.         setState(-1); // inhibit interrupts until runWorker  
  22.         this.firstTask = firstTask;  
  23.         this.thread = getThreadFactory().newThread(this); 
  24.     }  
  25.     /** Delegates main run loop to outer runWorker. */  
  26.     public void run() {  
  27.         runWorker(this);  
  28.     }  
  29.     // Lock methods  
  30.     //  
  31.     // The value 0 represents the unlocked state.  
  32.     // The value 1 represents the locked state.  
  33.     protected boolean isHeldExclusively() {  
  34.         return getState() != 0;  
  35.     }  
  36.     protected boolean tryAcquire(int unused) {  
  37.         if (compareAndSetState(0, 1)) { 
  38.              setExclusiveOwnerThread(Thread.currentThread());  
  39.             return true;  
  40.         }  
  41.         return false;  
  42.     }  
  43.     protected boolean tryRelease(int unused) {  
  44.         setExclusiveOwnerThread(null);  
  45.         setState(0);  
  46.         return true;  
  47.     }  
  48.     public void lock()        { acquire(1); }  
  49.     public boolean tryLock()  { return tryAcquire(1); }  
  50.     public void unlock()      { release(1); }  
  51.     public boolean isLocked() { return isHeldExclusively(); }  
  52.     void interruptIfStarted() {  
  53.         Thread t;  
  54.         if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { 
  55.             try {  
  56.                 t.interrupt();  
  57.             } catch (SecurityException ignore) {  
  58.             }  
  59.         }  
  60.     }  

最重要的構(gòu)造方法: 

  1. Worker(Runnable firstTask) { // worker本身實現(xiàn)了Runnable接口  
  2.         setState(-1); // inhibit interrupts until runWorker  
  3.         this.firstTask = firstTask; // 持有外部傳進來的runnable任務(wù)  
  4.         //創(chuàng)建了一個thread對象,并把自身這個runnable對象給了thread,一旦該thread執(zhí)行start方法,就會執(zhí)行worker的run方法  
  5.         this.thread = getThreadFactory().newThread(this);   
  6.     }  
  7. 在addWorker方法中執(zhí)行的t.start會去執(zhí)行worker的run方法:  
  8. public void run() {  
  9.         runWorker(this);  
  10.     } 
  11.  run方法又執(zhí)行了ThreadPoolExecutor的runWorker方法,把當前worker對象傳入。  
  12. final void runWorker(Worker w) {  
  13.     Thread wt = Thread.currentThread();  
  14.     Runnable task = w.firstTask; // 取出worker的runnable任務(wù)  
  15.     w.firstTask = null 
  16.     w.unlock(); // allow interrupts  
  17.     boolean completedAbruptly = true 
  18.     try {  
  19.         // 循環(huán)不斷的判斷任務(wù)是否為空,當?shù)谝粋€判斷為false的時候,即task為null,這個task啥時候為null呢?  
  20.         // 要么w.firstTask為null,還記得我們在execute方法第二步的時候,執(zhí)行addWorker的時候傳進來的runnable是null嗎?  
  21.         // 要么是執(zhí)行了一遍while循環(huán),在下面的finally中執(zhí)行了task=null;  
  22.         // 或者執(zhí)行第二個判斷,一旦不為空就會繼續(xù)執(zhí)行循環(huán)里的代碼。  
  23.         while (task != null || (task = getTask()) != null) {  
  24.             w.lock();  
  25.             // If pool is stopping, ensure thread is interrupted;  
  26.             // if not, ensure thread is not interrupted.  This  
  27.             // requires a recheck in second case to deal with 
  28.             // shutdownNow race while clearing interrupt  
  29.             if ((runStateAtLeast(ctl.get(), STOP) || 
  30.                  (Thread.interrupted() &&  
  31.                   runStateAtLeast(ctl.get(), STOP))) &&  
  32.                 !wt.isInterrupted()) 
  33.                  wt.interrupt();  
  34.             try {  
  35.                 beforeExecute(wt, task);  
  36.                 Throwable thrown = null 
  37.                 try {  
  38.                     task.run(); // 任務(wù)不為空,就會執(zhí)行任務(wù)的run方法,也就是runnable的run方法  
  39.                 } catch (RuntimeException x) {  
  40.                     thrown = x; throw x;  
  41.                 } catch (Error x) {  
  42.                     thrown = x; throw x;  
  43.                 } catch (Throwable x) {  
  44.                     thrown = x; throw new Error(x);  
  45.                 } finally {  
  46.                     afterExecute(task, thrown);  
  47.                 }  
  48.             } finally {  
  49.                 task = null; // 執(zhí)行完成置null,繼續(xù)下一個循環(huán)  
  50.                 w.completedTasks++;  
  51.                 w.unlock();  
  52.             }  
  53.         }  
  54.         completedAbruptly = false 
  55.     } finally {  
  56.         processWorkerExit(w, completedAbruptly);  
  57.     }  

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

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

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

3,執(zhí)行完成把runnable任務(wù)置為null。

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

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

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

玄機就在getTask方法中。 

  1. private Runnable getTask() {  
  2.     boolean timedOut = false; // Did the last poll() time out?  
  3.     for (;;) {  
  4.         int c = ctl.get();  
  5.         int rs = runStateOf(c);  
  6.         // Check if queue empty only if necessary.  
  7.         if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {  
  8.             decrementWorkerCount();  
  9.             return null;  
  10.         }  
  11.         int wc = workerCountOf(c);  
  12.         // timed變量用于判斷是否需要進行超時控制。  
  13.         // allowCoreThreadTimeOut默認是false,也就是核心線程不允許進行超時; 
  14.         // wc > corePoolSize,表示當前線程池中的線程數(shù)量大于核心線程數(shù)量;  
  15.         // 對于超過核心線程數(shù)量的這些線程或者允許核心線程進行超時控制的時候,需要進行超時控制  
  16.         // Are workers subject to culling?  
  17.         boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;  
  18.         // 如果需要進行超時控制,且上次從緩存隊列中獲取任務(wù)時發(fā)生了超時(timedOut開始為false,后面的循環(huán)末尾超時時會置為true)  
  19.         // 或者當前線程數(shù)量已經(jīng)超過了最大線程數(shù)量,那么嘗試將workerCount減1,即當前活動線程數(shù)減1,  
  20.         if ((wc > maximumPoolSize || (timed && timedOut))  
  21.             && (wc > 1 || workQueue.isEmpty())) {  
  22.             // 如果減1成功,則返回null,這就意味著runWorker()方法中的while循環(huán)會被退出,其對應(yīng)的線程就要銷毀了,也就是線程池中少了一個線程了  
  23.             if (compareAndDecrementWorkerCount(c))  
  24.                 return null;  
  25.             continue;  
  26.         }  
  27.         try {  
  28.             // 注意workQueue中的poll()方法與take()方法的區(qū)別  
  29.             //poll方式取任務(wù)的特點是從緩存隊列中取任務(wù),最長等待keepAliveTime的時長,取不到返回null  
  30.             //take方式取任務(wù)的特點是從緩存隊列中取任務(wù),若隊列為空,則進入阻塞狀態(tài),直到能取出對象為止  
  31.             Runnable r = timed ?  
  32.                 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :  
  33.                 workQueue.take();  
  34.             if (r != null)  
  35.                 return r;  
  36.             timedOut = true; // 能走到這里說明已經(jīng)超時了  
  37.         } catch (InterruptedException retry) {  
  38.             timedOut = false 
  39.         }  
  40.     }  

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

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

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

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

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

我們用個例子來看下:

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

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

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

小結(jié)

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

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

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

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

最后,關(guān)注公眾號Java技術(shù)棧,在后臺回復(fù):面試,可以獲取我整理的 Java 多線程系列面試題和答案,非常齊全。 

 

責(zé)任編輯:龐桂玉 來源: Java技術(shù)棧
相關(guān)推薦

2023-08-02 08:03:08

Python線程池

2021-11-29 10:55:11

線程池Java面試

2022-06-24 06:43:57

線程池線程復(fù)用

2012-05-15 02:18:31

Java線程池

2020-03-05 15:34:16

線程池C語言局域網(wǎng)

2022-03-23 08:51:21

線程池Java面試題

2023-05-19 08:01:24

Key消費場景

2021-09-11 15:26:23

Java多線程線程池

2024-11-13 16:37:00

Java線程池

2022-03-14 07:32:06

線程池拒絕策略自定義

2010-03-18 15:15:08

Java線程池

2024-10-21 18:12:14

2010-03-15 16:56:16

Java線程池

2024-04-02 08:27:19

異步任務(wù)抽象

2019-09-09 09:50:27

設(shè)置Java線程池

2020-10-27 13:24:35

線程池系統(tǒng)模型

2023-06-07 13:49:00

多線程編程C#

2023-11-22 08:37:40

Java線程池

2024-07-15 08:20:24

2020-12-10 08:24:40

線程池線程方法
點贊
收藏

51CTO技術(shù)棧公眾號