☕【Java技術指南】「技術盲區」看看線程池是如何回收和維持運作線程的核心技術體系

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"線程池的難點和重點","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"讓我們一起來看看線程池是如何回收和維持運作線程的核心技術體系。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"線程池的前提和介紹","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般來講JDK線程池就是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"ThreadPoolExecutor","attrs":{}},{"type":"text","text":",大多數會對線程池執行任務的流程有了大體瞭解,實際上這個流程也十分通俗易懂,就不再贅述了,我之前的文章也介紹過了相關的技術點分析和介紹說明。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"講一講線程池是如何回收線程的?","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"runWorker(Worker w)","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"線程執行的基本流程","attrs":{}}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"工作線程啓動後,就進入","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"runWorker(Worker w)","attrs":{}},{"type":"text","text":" 方法。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"內部是一個while循環,循環判斷任務是否爲空,若不爲空,執行任務;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"若取不到任務,或發生異常,退出循環,執行**processWorkerExit(w, completedAbruptly); **在這個方法裏把工作線程移除掉。","attrs":{}}]}]}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"讀取任務的方式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主要有兩種方式:一個是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"firstTask","attrs":{}},{"type":"text","text":",這個是工作線程第一次跑的時候執行的任務,最多隻能執行一次,後面得從","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"getTask","attrs":{}},{"type":"text","text":"方法裏取任務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"getTask是關鍵,在不考慮異常的場景下,返回,就表示退出循環,結束線程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private Runnable getTask() {\n boolean timedOut = false; // Did the last poll() time out?\n for (;;) {\n int c = ctl.get();\n // Check if queue empty only if necessary.\n if (runStateAtLeast(c, SHUTDOWN)\n && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {\n decrementWorkerCount();\n return null;\n }\n int wc = workerCountOf(c);\n // Are workers subject to culling?\n boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;\n if ((wc > maximumPoolSize || (timed && timedOut))\n && (wc > 1 || workQueue.isEmpty())) {\n if (compareAndDecrementWorkerCount(c))\n return null;\n continue;\n }\n try {\n Runnable r = timed ?\n workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :\n workQueue.take();\n if (r != null)\n return r;\n timedOut = true;\n } catch (InterruptedException retry) {\n timedOut = false;\n }\n }\n }\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"重點關注的是getTask返回操作","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"一共有兩種情況會返回","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"第一種情況,線程池的狀態已經是STOP,TIDYING, TERMINATED,或者是SHUTDOWN且工作隊列爲空;","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"if(runStateAtLeast(c, SHUTDOWN)\n && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {\n decrementWorkerCount();\n return null;\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"第二種情況,工作線程數已經大於最大線程數或當前工作線程已超時,且,還有其他工作線程或任務隊列爲空。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" if ((wc > maximumPoolSize || (timed && timedOut))\n && (wc > 1 || workQueue.isEmpty())) {\n if (compareAndDecrementWorkerCount(c))\n return null;\n continue;\n}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"線程池回收工作線程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"未調用shutdown ,RUNNING狀態下全部任務執行完成的場景。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種場景,會將工作線程的數量減少到核心線程數大小(如果本來就沒有超過,則不需要回收)。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"案例場景分析","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如一個線程池,核心線程數爲4,最大線程數爲8。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"開始是4個工作線程,當任務把任務隊列塞滿,就得將工作線程增加到8。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當後面任務執行到差不多了,線程取不到任務了,就會回收到4個工作線程的狀態(取決於allowCoreThreadTimeOut的值,這裏討論默認值false的情況,即核心線程不會超時。如果爲true,工作線程可以全部銷燬)。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以先排除上面提到的條件1,線程池的狀態已經是STOP,TIDYING, TERMINATED,或者是SHUTDOWN且工作隊列爲空。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲線程池一直是RUNNING,這條判斷永遠是false。在這個場景中,可以當條件1不存在。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面分析取不出任務時線程是怎麼運行的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"從任務隊列取任務有兩種方式,超時等待還是可以一直阻塞下去。決定因素是timed變量。該變量在前面賦值,如果當前線程數大於核心線程數,變量timed爲true, 否則爲false(當然是在:allowCoreThreadTimeOut爲false的情況)。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在討論的是timed爲true的情況。keepAliveTime一般不設置,默認值爲0,所以基本上可以認爲是不阻塞,馬上返回取任務的結果,在線程超時等待喚醒之後,發現取不出任務,timeOut變爲true,進入下一次循環。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":2,"normalizeStart":2},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"來到1的判斷,線程池一直RUNNING, 不進入代碼塊。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"來到2的判斷,這時任務隊列爲空,條件成立,CAS減少線程數,若成功,返回,否則,重複1。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"注意,有可能多條線程同時通過2的判斷,那會不會減少後線程的數量反而比預想的核心線程數少呢?","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如當前線程數已經只有5條了,此時有兩條線程同時喚醒,通過2的判斷,同時減少數量,那剩下的線程數反而只有3條,和預期不一致。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實際上是不會的,爲了防止這種情況,compareAndDecrementWorkerCount(c) 用的是CAS方法,如果CAS失敗就continue,進入下一輪循環,重新判斷。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"像上述例子,其中一條線程會CAS失敗,然後重新進入循環,發現工作線程數已經只有4了,timed爲false,這條線程就不會被銷燬,可以一直阻塞了(workQueue.take)。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從這裏也可以看出,雖然有核心線程數,但線程並沒有區分是核心還是非核心,並不是先創建的就是核心,超過核心線程數後創建的就是非核心,最終保留哪些線程,完全隨機。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"shutdown","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"調用shutdown ,全部任務執行完成的場景","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這種場景,無論是核心線程還是非核心線程,所有工作線程都會被銷燬。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在調用shutdown之後,會向所有的空閒工作線程發送中斷信號。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public void shutdown() {\n final ReentrantLock mainLock = this.mainLock;\n mainLock.lock();\n try {\n checkShutdownAccess();\n advanceRunState(SHUTDOWN);\n interruptIdleWorkers();\n onShutdown(); // hook for ScheduledThreadPoolExecutor\n } finally {\n mainLock.unlock();\n }\n tryTerminate();\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最終傳入false,調用下面這個方法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private void interruptIdleWorkers(boolean onlyOne) {\n final ReentrantLock mainLock = this.mainLock;\n mainLock.lock();\n try {\n for (Worker w : workers) {\n Thread t = w.thread;\n if (!t.isInterrupted() && w.tryLock()) {\n try {\n t.interrupt();\n } catch (SecurityException ignore) {\n } finally {\n w.unlock();\n }\n }\n if (onlyOne)\n break;\n }\n } finally {\n mainLock.unlock();\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看出,在發出中斷信號前,會判斷是否已經中斷,以及要獲得工作線程的獨佔鎖。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"發出中斷信號的時候,工作線程要麼在getTask裏準備獲取任務,要麼在執行任務,那就得等它執行完當前任務纔會發出,因爲工作線程在執行任務的時候,也會工作線程加鎖。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"工作線程執行完任務,又跑到getTask裏面去了。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我們只要看getTask裏面怎麼應對中斷異常的就可以了。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"工作線程在getTask裏,有兩種可能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"final void runWorker(Worker w) {\n Thread wt = Thread.currentThread();\n Runnable task = w.firstTask;\n w.firstTask = null;\n w.unlock(); // allow interrupts\n boolean completedAbruptly = true;\n try {\n while (task != null || (task = getTask()) != null) {\n w.lock();\n // If pool is stopping, ensure thread is interrupted;\n // if not, ensure thread is not interrupted. This\n // requires a recheck in second case to deal with\n // shutdownNow race while clearing interrupt\n if ((runStateAtLeast(ctl.get(), STOP) ||\n (Thread.interrupted() &&\n runStateAtLeast(ctl.get(), STOP))) &&\n !wt.isInterrupted())\n wt.interrupt();\n try {\n beforeExecute(wt, task);\n try {\n task.run();\n afterExecute(task, null);\n } catch (Throwable ex) {\n afterExecute(task, ex);\n throw ex;\n }\n } finally {\n task = null;\n w.completedTasks++;\n w.unlock();\n }\n }\n completedAbruptly = false;\n } finally {\n processWorkerExit(w, completedAbruptly);\n }\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"任務已全部完成,線程在阻塞等待。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"中斷信號將其喚醒,從而進入下一輪循環。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到達1處,符合條件,減少工作線程數量,並返回,由外層結束這條線程。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏的decrementWorkerCount是自旋式的,一定會減1。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"任務還沒有完全執行完","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"調用shutdown之後,未執行完的任務要執行完畢,線程池才能結束。所以此時有可能線程還在工作。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"分兩個階段討論","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"階段1 任務較多,工作線程都能獲得任務","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"分析一下收到中斷信號後線程的表現。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設有線程A,正通過getTask裏獲取任務。此時A被中斷,在獲取任務時,無論是poll還是take,都會拋出中斷異常。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"異常被捕獲,重新進入下一輪循環,只要隊列不爲空,就可以繼續取任務。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"workQueue是BlockingQueue類型,以常見的LinkedBlockingQueue和ArrayBlockingQueue爲例,加鎖時都是調用lockInterruptibly,是響應中斷的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該方法又調用了AQS的acquireInterruptibly(int arg)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"acquireInterruptibly(int arg),無論是在入口處判斷中斷異常,還是在parkAndCheckInterrupt方法阻塞,被中斷喚醒並判斷中斷異常時,均使用了Thread.interrupted。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個方法會返回線程的中斷狀態,並把中斷狀態重置!也就是說,線程不再是中斷狀態了,這樣在再次取任務時,就不會報錯了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此,這對於正在準備取任務的線程,只是相當於浪費了一次循環,這可能是線程中斷帶來的副作用吧,當然,對整體的運行不影響。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"任務剛好要執行完了","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這時任務已經快取完了,比如有4條工作線程,只剩下2個任務,那就可能出現2條線程獲得任務,2條線程阻塞。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲在獲取任務前的判斷,沒有加鎖,那麼會不會出現,所有線程都通過了前面的校驗,來到workQueue獲取任務的地方,剛好任務隊列已經空了,線程全部阻塞了呢?因爲shutdown 已經執行完畢,無法再向線程發出中斷信號,從而線程一直在阻塞,無法被回收。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設有A,B,C,D四條工作線程,同時通過了條件1和條件2的判斷,來到取任務的地方。那麼,工作隊列至少還有一個任務,至少會有一條線程能取到任務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設A,B獲得了任務,C,D阻塞。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"A, B接下來的步驟是:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"任務執行完成後,再次getTask,此時符合條件1,返回,線程準備被回收。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"processWorkerExit(Worker w, boolean completedAbruptly) 將線程回收。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回收就只是把線程幹掉這麼簡單嗎?來看看processWorkerExit(Worker w, boolean completedAbruptly) 的方法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,在裏面除了workers.remove(w) 移除線,還調用了tryTerminate。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一個判斷條件沒有一個子條件符合,跳過。第二個條件,工作線程還存在,那麼隨機中斷一條空閒線程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼問題就來了,中斷一條空閒線程,也沒說是一定中斷正在阻塞的線程啊。如果A, B同時退出,有沒有可能出現A中斷B, B中斷A,AB互相中斷,從而沒有線程去中斷喚醒阻塞的線程呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設A能走到這裏,說明A已經從工作線程的集合workers裏面移除了(processWorkerExit(Worker w, boolean completedAbruptly) 在tryTerminate()之前,已經將其移除)。那麼A中斷B,B來到這裏中斷,就不會在workers裏面找到A了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也就是說,退出的線程不能互相中斷,我從集合中退出後,中斷了你,你不能中斷我,因爲我已經退出集合,你只能中斷別人。那麼,即使有N個線程同時退出,至少在最後,也會有一條線程,會中斷剩餘的阻塞線程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"==阻塞的C,D中的任意一條被中斷喚醒後,又會重複step1的動作,週而復始,直到所有阻塞線程都被中斷,喚醒。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這也是爲什麼在tryTerminate裏面,傳入false,只需要中斷任意一條空閒線程的原因。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"總結","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor回收工作線程,一條線程getTask返回,就會被回收。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"兩種場景。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"未調用shutdown ,RUNNING狀態下全部任務執行完成的場景","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程數量大於corePoolSize,線程超時阻塞,超時喚醒後CAS減少工作線程數,如果CAS成功,返回,線程回收。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"否則進入下一次循環。當工作者線程數量小於等於corePoolSize,就可以一直阻塞了。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"調用shutdown ,全部任務執行完成的場景","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"shutdown 會向所有線程發出中斷信號,這時有兩種可能。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"所有線程都在阻塞","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"中斷喚醒,進入循環,都符合第一個if判斷條件,都返回,所有線程回收。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"任務還沒有完全執行完","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至少會有一條線程被回收。在processWorkerExit(Worker w, boolean completedAbruptly)方法裏會調用tryTerminate,向任意空閒線程發出中斷信號。所有被阻塞的線程,最終都會被一個個喚醒,回收。","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章