☕【Java技術之旅】走進線程池的世界(基礎篇)

{"type":"doc","content":[{"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":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong","attrs":{}}],"text":"本章內容屬於線程原理分析專題的基礎篇,之後還有源碼篇和深入篇。","attrs":{}}]}],"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","marks":[{"type":"size","attrs":{"size":10}}],"text":" 線程的創建和銷燬對操作系統來說都是比較重量級的操作,所以通過線程池可以大大的減少操作系統爲創建線程所消耗的成本和資源。它的作者是:","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"Doug Lea","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"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","marks":[{"type":"size","attrs":{"size":10}}],"text":" 線程池(","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"ThreadPoolExecutor","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":")是一種基於池化思想管理線程的工具,經常出現在多線程服務器中,如MySQL。線程過多會帶來額外的開銷,其中包括創建銷燬線程的開銷、調度線程的開銷等等,同時也降低了計算機的整體性能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" ","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"線程池維護多個線程","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":",","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"等待監督管理者分配可併發執行的任務","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":"。","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"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","marks":[{"type":"size","attrs":{"size":10}}],"text":" 而本文描述線程池是JDK中提供的","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"ThreadPoolExecutor","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":"類。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":" 當然,使用線程池可以帶來一系列好處:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"降低資源消耗","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":":通過池化技術重複利用已創建的線程,降低線程創建和銷燬造成的損耗。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"提高響應速度","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":":任務到達時,無需等待線程創建即可立即執行。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"提高線程的可管理性","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":":線程是稀缺資源,如果無限制創建,不僅會消耗系統資源,還會因爲線程的不合理分佈導致資源調度失衡,降低系統的穩定性。使用線程池可以進行統一的分配、調優和監控。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"提供更多更強大的功能","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":":線程池具備可拓展性,允許開發人員向其中增加更多的功能。比如延時定時線程池","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"ScheduledThreadPoolExecutor","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":",就允許任務延期執行或定期執行。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"🚀 線程池的運作流程","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":1,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"當一個任務被提交後,線程池首先檢查正在運行的線程數是否達到核心線程數,如果未達到則創建一個線程。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"如果線程池內正在運行的線程數已經達到了核心線程數,任務將會被放到 BlockingQueue 內。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"如果 BlockingQueue 已滿,線程池將會嘗試將線程數擴充到最大線程池容量。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":4,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"如果當前線程池內線程數量已經達到最大線程池容量,則會執行拒絕策略拒絕任務提交。","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"整體流程如下圖所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0c/0c675ff915f05ffd3db886e3ed139c4d.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"border"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":" 流程描述沒有問題,但如果某些點未經過推敲,容易導致誤解,而且描述中的情境太理想化,如果配置時不考慮運行時環境,也會出現一些非常詭異的問題。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"📚 線程池的構造器","attrs":{}}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":"使用線程池離不開 ThreadPoolExecutor 類,該類實現了 ExecutorService 接口,其構造方法如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public ThreadPoolExecutor(int corePoolSize,\t\n int maximumPoolSize,\t\n long keepAliveTime,\t\n TimeUnit unit,\t\n BlockingQueue workQueue,\t\n ThreadFactory threadFactory,\t\n RejectedExecutionHandler handler);","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","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"corePoolSize:核心池大小","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"maximumPoolSize:線程池大小(maximumPoolSize >= corePoolSize)","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"keepAliveTime:沒有任務時線程的存活時間,默認情況下只有線程數目大於 corePoolSize 時,此參數才起作用,若線程數目等於 corePoolSize,則這些線程會一直存活。但若調用 allowCoreThreadTimeOut(boolean)方法,則線程數目不大於 corePoolSize 時,此參數也起作用,直到線程數目爲 0","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"unit:keepAliveTime 的時間單位,有以下七種取值:","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#40A9FF","name":"blue"}},{"type":"strong","attrs":{}}],"text":"TimeUnit.DAYS;//天","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#40A9FF","name":"blue"}},{"type":"strong","attrs":{}}],"text":"TimeUnit.HOURS;//小時","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#40A9FF","name":"blue"}},{"type":"strong","attrs":{}}],"text":"TimeUnit.MINUTES;//分鐘","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#40A9FF","name":"blue"}},{"type":"strong","attrs":{}}],"text":"TimeUnit.SECONDS;//秒","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#40A9FF","name":"blue"}},{"type":"strong","attrs":{}}],"text":"TimeUnit.MILLISECONDS;//毫秒","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#40A9FF","name":"blue"}},{"type":"strong","attrs":{}}],"text":"TimeUnit.MICROSECONDS;//微秒","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#40A9FF","name":"blue"}},{"type":"strong","attrs":{}}],"text":"TimeUnit.NANOSECONDS;//納秒","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"workQueue:指定構成緩衝區的阻塞隊列,即指定了線程池的排隊策略,常用的有以下三種:","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#40A9FF","name":"blue"}},{"type":"strong","attrs":{}}],"text":"ArrayBlockingQueue:基於數組的先進先出隊列,此隊列創建時必須指定大小","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#40A9FF","name":"blue"}},{"type":"strong","attrs":{}}],"text":"LinkedBlockingQueue:基於鏈表的先進先出隊列,如果創建時沒有指定此隊列大小,則默認爲 Integer.MAX_VALUE","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#40A9FF","name":"blue"}},{"type":"strong","attrs":{}}],"text":"synchronousQueue:這個隊列不會保存提交的任務,而是將直接新建一個線程來執行新來的任務","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"threadFactory(可選):線程工廠,用來創建線程,可自定義","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"handler(可選):拒絕策略,有以下四種策略可選:","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#40A9FF","name":"blue"}},{"type":"strong","attrs":{}}],"text":"ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出 RejectedExecutionException 異常。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#40A9FF","name":"blue"}},{"type":"strong","attrs":{}}],"text":"ThreadPoolExecutor.DiscardPolicy:丟棄任務,不拋出異常。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#40A9FF","name":"blue"}},{"type":"strong","attrs":{}}],"text":"ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#40A9FF","name":"blue"}},{"type":"strong","attrs":{}}],"text":"ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"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","marks":[{"type":"size","attrs":{"size":10}}],"text":" 線程池內線程數量小於等於 ","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"coreSize","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":" 的部分我稱爲核心線程池,核心池是線程池的常駐部分,內部的線程一般不會被銷燬,我們提交的任務也應該絕大部分都由核心池內的線程來執行。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"📚 創建過程","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","marks":[{"type":"size","attrs":{"size":10}}],"text":" 默認情況下,線程池構造完成後是沒有線程的,需要等任務提交時纔會創建線程,如果需要在線程池構造完成時就創建線程,可以調用以下兩個方法:","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong","attrs":{}}],"text":"prestartCoreThread():初始化一個核心線程;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong","attrs":{}}],"text":"prestartAllCoreThreads():初始化所有核心線程","attrs":{}}]}],"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","marks":[{"type":"size","attrs":{"size":10}}],"text":" 任務提交有兩種方法,execute()和 submit()","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong","attrs":{}}],"text":"void execute(Runnable task),無返回值","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong","attrs":{}}],"text":"Futuresubmit(Runnable task, T result) ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong","attrs":{}}],"text":"Futuresubmit(Callable task),有返回值","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":" 使用jstack 打印出線程棧並數一下線程池內線程數量,會發現線程池內的線程數會隨着任務的提交而逐漸增大,直到達到 coreSize。因爲核心池的設計初衷是想它能作爲常駐池,承載日常流量,所以它應該被儘快初始化,於是線程池的邏輯是在沒有達到 coreSize 之前,每一個任務都會創建一個新的線程,對應的源碼爲:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" public void execute(Runnable command) {\n ...\n int c = ctl.get();\n // workerCountOf() 方法是獲取線程池內線程數量\n if (workerCountOf(c) < corePoolSize) { \n if (addWorker(command, true))\n return;\n c = ctl.get();\n }\n ...\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":" 我們也知道線程被創建後,會在一個 while 循環裏嘗試從 BlockingQueue 裏獲取並執行任務,說它正在 running 也不爲過。基於此,我們對一些高併發服務進行的預熱,","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"其實並不是期望 JVM 能對熱點代碼做 JIT 等優化,對線程池、連接池和本地緩存的預熱纔是重點","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"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","marks":[{"type":"size","attrs":{"size":10}}],"text":" BlockingQueue是線程池內的另一個重要組件,首先它是線程池”生產者-消費者”模型的中間媒介,另外它也可以爲大量突發的流量做緩衝,但理解和配置它也經常會出錯。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":" 基於”生產者-消費者”模型,我們可能會認爲如果配置了足夠的消費者,線程池就不會有任何問題。其實不然,我們還必須考慮併發量這一因素。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":" 設想以下情況:有 1000 個任務要同時提交到線程池內併發執行,在線程池被初始化完成的情況下,它們都要被放到 BlockingQueue 內等待被消費,在極限情況下,消費線程一個任務也沒有執行完成,那麼這 1000 個請求需要同時存在於 BlockingQueue 內,如果配置的 BlockingQueue Size 小於 1000,多餘的請求就會被拒絕。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":" 那麼這種極限情況發生的概率有多大呢?答案是非常大,因爲操作系統對 I/O 線程的調度優先級是非常高的,一般我們的任務都是由 I/O 的準備或完成(如 tomcat 受理了 http 請求)開始的,所以很有可能被調度到的都是 tomcat 線程,它們在一直往線程池內提交請求,而消費者線程卻調度不到,導致請求堆積。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":" 我負責的服務就發生過這種請求被異常拒絕的情況,壓測時 QPS 2000,平均響應時間爲 20ms,正常情況下,40 個線程就可以平衡生產速度,不會堆積。但在 BlockingQueue Size 爲 50 時,即使線程池 coreSize 爲 1000,還會出現請求被線程池拒絕的情況。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":" 這種情況下,BlockingQueue 的重要的意義就是它是一個能長時間存儲任務的容器,能以很小的代價爲線程池提供緩衝。根據上文可知,線程池能支持","attrs":{}},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":"BlockingQueue Size","attrs":{}}],"attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":"個任務同時提交,我們把最大同時提交的任務個數,稱爲併發量,配置線程池時,瞭解併發量異常重要","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"🚀 GC回收問題","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":"Full GC 是 Stop the World 的,但這裏的 World 指的是 JVM,而一個請求 I/O 的準備和完成是操作系統在進行的,JVM 停止了,但操作系統還是會正常受理請求,在 JVM 恢復後執行,所以 GC 是會堆積請求的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":"上文中提到的併發量計算一定要考慮到 GC 時間內堆積的請求同時被受理的情況,堆積的請求數可以通過 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":"QPS*GC時間","attrs":{}}],"attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"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","marks":[{"type":"size","attrs":{"size":10}}],"text":"除此之外,配置線程池參數時,一定要考慮業務場景。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":"假如接口的流量大部分來自於一個定時程序,那麼平均 QPS 就沒有了任何意義,線程池設計時就要考慮給 BlockingQueue 的 Size 設置一個大一些的值;而如果流量非常不平均,一天內只有某一小段時間纔有高流量的話,而且線程資源緊張的情況下,就要考慮給線程池的 maxSize 留下較大的冗餘;在流量尖刺明顯而響應時間不那麼敏感時,也可以設置較大的 BlockingQueue,允許任務進行一定程度的堆積。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"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","marks":[{"type":"size","attrs":{"size":10}}],"text":" ","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"我們常用 QPS 來衡量服務壓力,所以配置線程池參數時也經常參考這個值,但有時候 QPS 和併發量有時候相關性並沒有那麼高,QPS 還要","attrs":{}},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"搭配任務執行時間","attrs":{}}],"attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"來","attrs":{}},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"推算","attrs":{}}],"attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":"峯值併發量。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong","attrs":{}}],"text":" 比如請求間隔嚴格相同的接口,平均 QPS 爲 1000,它的併發量峯值是多少呢?我們並沒有辦法估算,因爲如果任務 執行時間爲 1ms,那麼它的併發量只有 1;而如果任務執行時間爲 1s,那麼併發量峯值爲 1000。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"strong","attrs":{}}],"text":" 可是知道了任務執行時間,就能算出併發量了嗎?也不能,因爲如果請求的間隔不同,可能 1min 內的請求都在一秒內發過來,那這個併發量還要乘以 60,所以上面才說知道了 QPS 和任務執行時間,併發量也只能靠推算。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong","attrs":{}}],"text":" 計算併發量,我一般的經驗值是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong","attrs":{}}],"text":"QPS*平均響應時間","attrs":{}}],"attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong","attrs":{}}],"text":",再留上一倍的冗餘,但如果業務重要的話,BlockingQueue Size 設置大一些也無妨(1000 或以上),畢竟每個任務佔用的內存量很有限。","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":" ","attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":"明確的一點是線程池並沒有準確的調度功能,即它無法感知有哪些線程是處於空閒狀態的,並把提交的任務派發給空閒線程。線程池採用的是”生產者-消費者”模式,除了觸發線程創建的任務(線程的 firstTask)不會入 BlockingQueue 外,其他任務都要進入到 BlockingQueue,等待線程池內的線程消費,而任務會被哪個線程消費到完全取決於操作系統的調度。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":10}}],"text":"對應的生產者源碼如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"    public void execute(Runnable command) {\n        ...\n        if (isRunning(c) && workQueue.offer(command)) { \n isRunning() 是判斷線程池處理戚狀態\n            int recheck = ctl.get();\n            if (! isRunning(recheck) && remove(command))\n                reject(command);\n            else if (workerCountOf(recheck) == 0)\n                addWorker(null, false);\n        }\n        ...\n    }","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對應的消費者源碼如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private Runnable getTask() {\n        for (;;) {\n            ...\n            Runnable r = timed ?\n                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :\n                workQueue.take();\n            if (r != null)\n                return r;\n            ...\n        }\n    }","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"🚀 可選線程池模型","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"📚 newCachedThreadPool(會創建過多的線程任務)","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L, \n TimeUnit.SECONDS,new SynchronousQueue())","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"📚 newFixedThreadPool(會擠壓過多的任務到隊列裏)","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"new ThreadPoolExecutor(nThreads,nThreads,0L, \n TimeUnit.MILLISECONDS,new LinkedBlockingQueue())","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"📚 newSingleThreadPool(會擠壓過多的任務到隊列裏)","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"new ThreadPoolExecutor(1,1,0L,\n TimeUnit.MILLISECONDS,new LinkedBlockingQueue())","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"📚 newScheduledThreadPool(會創建過多的線程任務)","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"new ScheduledThreadPoolExecutor(nThreads,Integer.MAX_VALUE,0L,\n TimeUnit.NANOSECONDS,new DelayedWorkQueue())","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":"size","attrs":{"size":10}},{"type":"color","attrs":{"color":"#F5222D","name":"user"}},{"type":"strong","attrs":{}}],"text":"調用實例:","attrs":{}},{"type":"codeinline","marks":[{"type":"strong"}],"attrs":{}},{"type":"text","marks":[{"type":"size","attrs":{"size":10}},{"type":"color","attrs":{"color":"#F5222D","name":"user"}}],"text":"構造線程池時優先選用線程池模型,如果這些模型不能滿足要求,再自定義 ThreadPoolExecutor 線程池","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"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","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong","attrs":{}}],"text":"shutdown():不會立即關閉線程池,而是不再接受新的任務,等當前所有任務處理完之後關閉線程池","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":9}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong","attrs":{}}],"text":"shutdownNow():立即關閉線程池,打斷正在執行的任務,清空緩衝隊列,返回尚未執行的任務","attrs":{}}]}],"attrs":{}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章