線程與線程池的那些事之線程池篇(萬字長文)

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"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":"codeinline","content":[{"type":"text","text":"線程","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"線程池","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"單線程","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"多線程","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"線程池的好處","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"線程回收","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"創建方式","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"核心參數","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"底層機制","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"拒絕策略","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"參數設置","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"動態監控","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","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":"線程和線程池相關的知識,是Java學習或者面試中一定會遇到的知識點,本篇我們會從線程和進程,並行與併發,單線程和多線程等,一直講解到線程池,線程池的好處,創建方式,重要的核心參數,幾個重要的方法,底層實現,拒絕策略,參數設置,動態調整,線程隔離等等。主要的大綱如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c5/c591ff5f6c93bc0dce66a8103082e5d7.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"線程池的好處","attrs":{}}]},{"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":"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":{}}]}]}],"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":"降低資源消耗:池化技術可以重複利用已經創建的線程,降低線程創建和銷燬的損耗。","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":"管理線程可控:線程是稀缺資源,不能無限創建,線程池可以做到統一分配和監控","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":"其實池化技術,用在比較多地方,比如:","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":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"線程池相關的類","attrs":{}}]},{"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":"image","attrs":{"src":"https://static001.geekbang.org/infoq/91/91dbb496ba05a930cb4347729d88ddc9.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Executor","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Executor","attrs":{}}],"attrs":{}},{"type":"text","text":" 是頂級接口,裏面只有一個方法","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"execute(Runnable command)","attrs":{}}],"attrs":{}},{"type":"text","text":",定義的是調度線程池來執行任務,它定義了線程池的基本規範,執行任務是它的天職。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ExecutorService","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"ExecutorService","attrs":{}}],"attrs":{}},{"type":"text","text":" 繼承了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Executor","attrs":{}}],"attrs":{}},{"type":"text","text":",但是它仍然是一個接口,它多了一些方法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9a/9a6b75f7dddfe76eaa8288184dcb4067.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"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":"codeinline","content":[{"type":"text","text":"void shutdown()","attrs":{}}],"attrs":{}},{"type":"text","text":":關閉線程池,會等待任務執行完。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"List shutdownNow()","attrs":{}}],"attrs":{}},{"type":"text","text":":立刻關閉線程池,嘗試停止所有正在積極執行的任務,停止等待任務的處理,並","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"返回一個正在等待執行的任務列表(還沒有執行的)","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"boolean isShutdown()","attrs":{}}],"attrs":{}},{"type":"text","text":":判斷線程池是不是已經關閉,但是可能線程還在執行。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"boolean isTerminated()","attrs":{}}],"attrs":{}},{"type":"text","text":":在執行shutdown/shutdownNow之後,所有的任務已經完成,這個狀態就是true。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"boolean awaitTermination(long timeout, TimeUnit unit)","attrs":{}}],"attrs":{}},{"type":"text","text":":執行shutdown之後,阻塞等到terminated狀態,除非超時或者被打斷。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":" Future submit(Callable task)","attrs":{}}],"attrs":{}},{"type":"text","text":": 提交一個有返回值的任務,並且返回該任務尚未有結果的Future,調用future.get()方法,可以返回任務完成的時候的結果。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":" Future submit(Runnable task, T result)","attrs":{}}],"attrs":{}},{"type":"text","text":":提交一個任務,傳入返回結果,這個result沒有什麼作用,只是指定類型和一個返回的結果。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Future> submit(Runnable task)","attrs":{}}],"attrs":{}},{"type":"text","text":": 提交任務,返回Future","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":" List> invokeAll(Collection extends Callable> tasks)","attrs":{}}],"attrs":{}},{"type":"text","text":":批量執行tasks,獲取Future的list,可以批量提交任務。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":" List> invokeAll(Collection extends Callable> tasks,long timeout, TimeUnit unit)","attrs":{}}],"attrs":{}},{"type":"text","text":":批量提交任務,並指定超時時間","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":" T invokeAny(Collection extends Callable> tasks)","attrs":{}}],"attrs":{}},{"type":"text","text":": 阻塞,獲取第一個完成任務的結果值,","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":" T invokeAny(Collection extends Callable> tasks,long timeout, TimeUnit unit)","attrs":{}}],"attrs":{}},{"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":"可能有同學對前面的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":" Future submit(Runnable task, T result)","attrs":{}}],"attrs":{}},{"type":"text","text":"有疑問,這個reuslt有什麼作用?","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":"codeinline","content":[{"type":"text","text":"future.get()","attrs":{}}],"attrs":{}},{"type":"text","text":"返回這個結果,用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"result","attrs":{}}],"attrs":{}},{"type":"text","text":" new 了一個 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ftask","attrs":{}}],"attrs":{}},{"type":"text","text":",其內部其實是使用了Runnable的包裝類 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"RunnableAdapter","attrs":{}}],"attrs":{}},{"type":"text","text":",沒有對result做特殊的處理,調用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"call()","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法的時候,直接返回這個結果。(Executors 中具體的實現)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" public Future submit(Runnable task, T result) {\n if (task == null) throw new NullPointerException();\n RunnableFuture ftask = newTaskFor(task, result);\n execute(ftask);\n return ftask;\n }\n\n static final class RunnableAdapter implements Callable {\n final Runnable task;\n final T result;\n RunnableAdapter(Runnable task, T result) {\n this.task = task;\n this.result = result;\n }\n public T call() {\n task.run();\n // 返回傳入的結果\n return result;\n }\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":"還有一個方法值得一提:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"invokeAny()","attrs":{}}],"attrs":{}},{"type":"text","text":": 在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ThreadPoolExecutor","attrs":{}}],"attrs":{}},{"type":"text","text":"中使用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ExecutorService","attrs":{}}],"attrs":{}},{"type":"text","text":" 中的方法 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"invokeAny()","attrs":{}}],"attrs":{}},{"type":"text","text":" 取得第一個完成的任務的結果,當第一個任務執行完成後,會調用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"interrupt()","attrs":{}}],"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":"注意,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ExecutorService","attrs":{}}],"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":"可以看到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ExecutorService","attrs":{}}],"attrs":{}},{"type":"text","text":" 定義的是線程池的一些操作,包括關閉,判斷是否關閉,是否停止,提交任務,批量提交任務等等。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"AbstractExecutorService","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"AbstractExecutorService","attrs":{}}],"attrs":{}},{"type":"text","text":" 是一個抽象類,實現了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ExecutorService","attrs":{}}],"attrs":{}},{"type":"text","text":"接口,這是大部分線程池的基本實現,定時的線程池先不關注,主要的方法如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/90/90125abb1e14439db0cdd3e0634a3535.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"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":"codeinline","content":[{"type":"text","text":"submit","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"invokeAll","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"invokeAny","attrs":{}}],"attrs":{}},{"type":"text","text":" 等方法,而且提供了一個 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"newTaskFor","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法用於構建 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"RunnableFuture","attrs":{}}],"attrs":{}},{"type":"text","text":" 對象,那些能夠獲取到任務返回結果的對象都是通過 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"newTaskFor","attrs":{}}],"attrs":{}},{"type":"text","text":" 來獲取的。不展開裏面所有的源碼的介紹,僅以submit()方法爲例:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" public Future> submit(Runnable task) {\n if (task == null) throw new NullPointerException();\n // 封裝任務\n RunnableFuture ftask = newTaskFor(task, null);\n // 執行任務\n execute(ftask);\n // 返回 RunnableFuture 對象\n return ftask;\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":"但是在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AbstractExecutorService","attrs":{}}],"attrs":{}},{"type":"text","text":" 是沒有對最最重要的方法進行實現的,也就是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"execute()","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法。線程池具體是怎麼執行的,這個不同的線程池可以有不同的實現,一般都是繼承 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AbstractExecutorService","attrs":{}}],"attrs":{}},{"type":"text","text":" (定時任務有其他的接口),我們最最常用的就是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ThreadPoolExecutor","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/44/4482409d96ebfd2488372d3f1fba0587.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ThreadPoolExecutor","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"重點來了!!!","attrs":{}},{"type":"text","text":" ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ThreadPoolExecutor","attrs":{}}],"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":"先看","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ThreadPoolExecutor","attrs":{}}],"attrs":{}},{"type":"text","text":"的內部結構(屬性):","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class ThreadPoolExecutor extends AbstractExecutorService {\n // 狀態控制,主要用來控制線程池的狀態,是核心的遍歷,使用的是原子類\n private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));\n // 用來表示線程數量的位數(使用的是位運算,一部分表示線程的數量,一部分表示線程池的狀態)\n // SIZE = 32 表示32位,那麼COUNT_BITS就是29位\n private static final int COUNT_BITS = Integer.SIZE - 3;\n // 線程池的容量,也就是27位表示的最大值\n private static final int CAPACITY = (1 << COUNT_BITS) - 1;\n\n // 狀態量,存儲在高位,32位中的前3位\n // 111(第一位是符號位,1表示負數),線程池運行中\n private static final int RUNNING = -1 << COUNT_BITS; \n // 000\n private static final int SHUTDOWN = 0 << COUNT_BITS;\n // 001\n private static final int STOP = 1 << COUNT_BITS;\n // 010\n private static final int TIDYING = 2 << COUNT_BITS;\n // 011\n private static final int TERMINATED = 3 << COUNT_BITS;\n\n // 取出運行狀態\n private static int runStateOf(int c) { return c & ~CAPACITY; }\n // 取出線程數量\n private static int workerCountOf(int c) { return c & CAPACITY; }\n // 用運行狀態和線程數獲取ctl\n private static int ctlOf(int rs, int wc) { return rs | wc; }\n \n // 任務等待隊列\n private final BlockingQueue workQueue;\n // 可重入主鎖(保證一些操作的線程安全)\n private final ReentrantLock mainLock = new ReentrantLock();\n // 線程的集合\n private final HashSet workers = new HashSet();\n \n // 在Condition中,用await()替換wait(),用signal()替換notify(),用signalAll()替換notifyAll(),\n // 傳統線程的通信方式,Condition都可以實現,Condition和傳統的線程通信沒什麼區別,Condition的強大之處在於它可以爲多個線程間建立不同的Condition\n private final Condition termination = mainLock.newCondition();\n \n // 最大線程池大小\n private int largestPoolSize;\n // 完成的任務數量\n private long completedTaskCount;\n // 線程工廠\n private volatile ThreadFactory threadFactory;\n // 任務拒絕處理器\n private volatile RejectedExecutionHandler handler;\n // 非核心線程的存活時間\n private volatile long keepAliveTime;\n // 允許核心線程的超時時間\n private volatile boolean allowCoreThreadTimeOut;\n // 核心線程數\n private volatile int corePoolSize;\n // 工作線程最大容量\n private volatile int maximumPoolSize;\n // 默認的拒絕處理器(丟棄任務)\n private static final RejectedExecutionHandler defaultHandler =\n new AbortPolicy();\n // 運行時關閉許可\n private static final RuntimePermission shutdownPerm =\n new RuntimePermission(\"modifyThread\");\n // 上下文\n private final AccessControlContext acc;\n // 只有一個線程\n private static final boolean ONLY_ONE = true;\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":"從上面的代碼可以看出,用一個32位的對象保存線程池的狀態以及線程池的容量,高3位是線程池的狀態,而剩下的29位,則是保存線程的數量:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" // 狀態量,存儲在高位,32位中的前3位\n // 111(第一位是符號位,1表示負數),線程池運行中\n private static final int RUNNING = -1 << COUNT_BITS; \n // 000\n private static final int SHUTDOWN = 0 << COUNT_BITS;\n // 001\n private static final int STOP = 1 << COUNT_BITS;\n // 010\n private static final int TIDYING = 2 << COUNT_BITS;\n // 011\n private static final int TERMINATED = 3 << COUNT_BITS;\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":"各種狀態之間是不一樣的,他們的狀態之間變化如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8d/8d9dae20a9436627fe9465b0a9a43108.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"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":"RUNNING:運行狀態,可以接受任務,也可以處理任務","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":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"STOP:不可以接受任務,也不可以處理任務,中斷當前任務","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TIDYING:所有線程停止","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TERMINATED:線程池的最後狀態","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Worker 實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程池,肯定得有池子,並且是放線程的地方,在 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ThreadPoolExecutor","attrs":{}}],"attrs":{}},{"type":"text","text":" 中表現爲 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Worker","attrs":{}}],"attrs":{}},{"type":"text","text":",這是內部類:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/01/011a2f829b87221becdb7b9dd50f7c1a.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"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":"codeinline","content":[{"type":"text","text":"Worker","attrs":{}}],"attrs":{}},{"type":"text","text":" (打工人,不斷的領取任務,完成任務)的集合,這裏使用的是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"HashSet","attrs":{}}],"attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private final HashSet workers = new HashSet();\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":"codeinline","content":[{"type":"text","text":"Worker","attrs":{}}],"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":"codeinline","content":[{"type":"text","text":"Worker","attrs":{}}],"attrs":{}},{"type":"text","text":" 除了繼承了 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AbstractQueuedSynchronizer","attrs":{}}],"attrs":{}},{"type":"text","text":",也就是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AQS","attrs":{}}],"attrs":{}},{"type":"text","text":" , ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AQS","attrs":{}}],"attrs":{}},{"type":"text","text":" 本質上就是個隊列鎖,一個簡單的互斥鎖,一般是在中斷或者修改 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"worker","attrs":{}}],"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":"內部引入","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"AQS","attrs":{}}],"attrs":{}},{"type":"text","text":",是爲了線程安全,線程執行任務的時候,調用的是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"runWorker(Worker w)","attrs":{}}],"attrs":{}},{"type":"text","text":",這個方法不是worker的方法,而是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ThreadPoolExecutor","attrs":{}}],"attrs":{}},{"type":"text","text":"的方法。從下面的代碼可以看出,每次修改","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Worke","attrs":{}}],"attrs":{}},{"type":"text","text":"r的狀態的時候,都是線程安全的。","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Worker","attrs":{}}],"attrs":{}},{"type":"text","text":"裏面,持有了一個線程","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Thread","attrs":{}}],"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":"至於","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"runWorker(Worker w)","attrs":{}}],"attrs":{}},{"type":"text","text":"是怎麼運行的?先保持這個疑問,後面詳細講解。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" // 實現 Runnable,封裝了線程\n private final class Worker\n extends AbstractQueuedSynchronizer\n implements Runnable\n {\n // 序列化id\n private static final long serialVersionUID = 6138294804551838833L;\n\n // worker運行的線程\n final Thread thread;\n \n // 初始化任務,有可能是空的,如果任務不爲空的時候,其他進來的任務,可以直接運行,不在添加到任務隊列\n Runnable firstTask;\n // 線程任務計數器\n volatile long completedTasks;\n\n // 指定一個任務讓工人忙碌起來,這個任務可能是空的\n Worker(Runnable firstTask) {\n // 初始化AQS隊列鎖的狀態\n setState(-1); // 禁止中斷直到 runWorker\n this.firstTask = firstTask;\n // 從線程工廠,取出一個線程初始化\n this.thread = getThreadFactory().newThread(this);\n }\n\n // 實際上運行調用的是runWorker\n public void run() {\n // 不斷循環獲取任務進行執行\n runWorker(this);\n }\n\n // 0表示沒有被鎖\n // 1表示被鎖的狀態\n protected boolean isHeldExclusively() {\n return getState() != 0;\n }\n // 獨佔,嘗試獲取鎖,如果成功返回true,失敗返回false\n protected boolean tryAcquire(int unused) {\n // CAS 樂觀鎖\n if (compareAndSetState(0, 1)) {\n // 成功,當前線程獨佔鎖\n setExclusiveOwnerThread(Thread.currentThread());\n return true;\n }\n return false;\n }\n // 獨佔方式,嘗試釋放鎖\n protected boolean tryRelease(int unused) {\n setExclusiveOwnerThread(null);\n setState(0);\n return true;\n }\n // 上鎖,調用的是AQS的方法\n public void lock() { acquire(1); }\n // 嘗試上鎖\n public boolean tryLock() { return tryAcquire(1); }\n // 解鎖\n public void unlock() { release(1); }\n // 是否鎖住\n public boolean isLocked() { return isHeldExclusively(); }\n\n // 如果開始可就中斷\n void interruptIfStarted() {\n Thread t;\n if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {\n try {\n t.interrupt();\n } catch (SecurityException ignore) {\n }\n }\n }\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":"除了放線程池的地方,要是任務很多,沒有那麼多線程,肯定需要一個地方放任務,充當緩衝作用,也就是任務隊列,在代碼中表現爲:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private final BlockingQueue workQueue;\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":"計算機的內存總是有限的,我們不可能一直往隊列裏面增加內容,所以線程池爲我們提供了選擇,可以選擇多種隊列。同時當任務實在太多,佔滿了線程,並且把任務隊列也佔滿的時候,我們需要做出一定的反應,那就是拒絕還是拋出錯誤,丟掉任務?丟掉哪些任務,這些都是可能需要定製的內容。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"如何創建線程池","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於如何創建線程池,其實 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ThreadPoolExecutor","attrs":{}}],"attrs":{}},{"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":"核心線程數:核心線程數,一般是指常駐的線程,沒有任務的時候通常也不會銷燬","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":"非核心線程的存活時間:指的是沒有任務的時候,非核心線程能夠存活多久","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":"存放任務的隊列:用來存放任務","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":"拒絕處理器:如果添加任務失敗,將由該處理器處理","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" // 指定核心線程數,最大線程數,非核心線程沒有任務的存活時間,時間單位,任務隊列 \n public ThreadPoolExecutor(int corePoolSize,\n int maximumPoolSize,\n long keepAliveTime,\n TimeUnit unit,\n BlockingQueue workQueue) {\n this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,\n Executors.defaultThreadFactory(), defaultHandler);\n }\n // 指定核心線程數,最大線程數,非核心線程沒有任務的存活時間,時間單位,任務隊列,線程池工廠 \n public ThreadPoolExecutor(int corePoolSize,\n int maximumPoolSize,\n long keepAliveTime,\n TimeUnit unit,\n BlockingQueue workQueue,\n ThreadFactory threadFactory) {\n this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,\n threadFactory, defaultHandler);\n }\n // 指定核心線程數,最大線程數,非核心線程沒有任務的存活時間,時間單位,任務隊列,拒絕任務處理器\n public ThreadPoolExecutor(int corePoolSize,\n int maximumPoolSize,\n long keepAliveTime,\n TimeUnit unit,\n BlockingQueue workQueue,\n RejectedExecutionHandler handler) {\n this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,\n Executors.defaultThreadFactory(), handler);\n }\n // 最後其實都是調用了這個方法\n public ThreadPoolExecutor(int corePoolSize,\n int maximumPoolSize,\n long keepAliveTime,\n TimeUnit unit,\n BlockingQueue workQueue,\n ThreadFactory threadFactory,\n RejectedExecutionHandler handler) {\n ...\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":"其實,除了顯示的指定上面的參數之外,JDK也封裝了一些直接創建線程池的方法給我們,那就是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Executors","attrs":{}}],"attrs":{}},{"type":"text","text":":","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" // 固定線程數量的線程池,無界的隊列\n public static ExecutorService newFixedThreadPool(int nThreads) {\n return new ThreadPoolExecutor(nThreads, nThreads,\n 0L, TimeUnit.MILLISECONDS,\n new LinkedBlockingQueue());\n }\n // 單個線程的線程池,無界的隊列,按照任務提交的順序,串行執行 \n public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {\n return new FinalizableDelegatedExecutorService\n (new ThreadPoolExecutor(1, 1,\n 0L, TimeUnit.MILLISECONDS,\n new LinkedBlockingQueue(),\n threadFactory));\n }\n // 動態調節,沒有核心線程,全部都是普通線程,每個線程存活60s,使用容量爲1的阻塞隊列\n public static ExecutorService newCachedThreadPool() {\n return new ThreadPoolExecutor(0, Integer.MAX_VALUE,\n 60L, TimeUnit.SECONDS,\n new SynchronousQueue());\n }\n // 定時任務線程池\n public static ScheduledExecutorService newSingleThreadScheduledExecutor() {\n return new DelegatedScheduledExecutorService\n (new ScheduledThreadPoolExecutor(1));\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":"但是一般是不推薦使用上面別人封裝的線程池的哈!!!","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"線程池的底層參數以及核心方法","attrs":{}}]},{"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":"image","attrs":{"src":"https://static001.geekbang.org/infoq/eb/ebf5c72919a6fba8ad2e732040b7ef29.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"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":"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":"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":"codeinline","content":[{"type":"text","text":"keepAliveTime","attrs":{}}],"attrs":{}},{"type":"text","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":"提交任務,我們看","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"execute()","attrs":{}}],"attrs":{}},{"type":"text","text":",會先獲取線程池的狀態和個數,要是線程個數還沒達到核心線程數,會直接添加線程,否則會放到任務隊列,如果任務隊列放不下,會繼續增加線程,但是不是增加核心線程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" public void execute(Runnable command) {\n if (command == null)\n throw new NullPointerException();\n // 獲取狀態和個數\n int c = ctl.get();\n // 如果個數小於核心線程數\n if (workerCountOf(c) < corePoolSize) {\n // 直接添加\n if (addWorker(command, true))\n return;\n // 添加失敗則繼續獲取\n c = ctl.get();\n }\n // 判斷線程池狀態是不是運行中,任務放到隊列中\n if (isRunning(c) && workQueue.offer(command)) {\n // 再次檢查\n int recheck = ctl.get();\n // 判斷線程池是不是還在運行\n if (! isRunning(recheck) && remove(command))\n // 如果不是,那麼就拒絕並移除任務\n reject(command);\n else if (workerCountOf(recheck) == 0)\n // 如果線程數爲0,並且還在運行,那麼就直接添加\n addWorker(null, false);\n }else if (!addWorker(command, false))\n // 添加任務隊列失敗,拒絕\n reject(command);\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":"上面的源碼中,調用了一個重要的方法:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"addWorker(Runnable firstTask, boolean core)","attrs":{}}],"attrs":{}},{"type":"text","text":",該方法主要是爲了增加工作的線程,我們來看看它是如何執行的:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" private boolean addWorker(Runnable firstTask, boolean core) {\n // 回到當前位置重試\n retry:\n for (;;) {\n // 獲取狀態\n int c = ctl.get();\n int rs = runStateOf(c);\n\n // 大於SHUTDOWN說明線程池已經停止\n // ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()) 表示三個條件至少有一個不滿足\n // 不等於SHUTDOWN說明是大於shutdown\n // firstTask != null 任務不是空的\n // workQueue.isEmpty() 隊列是空的\n if (rs >= SHUTDOWN &&\n ! (rs == SHUTDOWN &&\n firstTask == null &&\n ! workQueue.isEmpty()))\n return false;\n\n for (;;) {\n // 工作線程數\n int wc = workerCountOf(c);\n // 是否符合容量\n if (wc >= CAPACITY ||\n wc >= (core ? corePoolSize : maximumPoolSize))\n return false;\n // 添加成功,跳出循環\n if (compareAndIncrementWorkerCount(c))\n break retry;\n c = ctl.get(); // Re-read ctl\n // cas失敗,重新嘗試\n if (runStateOf(c) != rs)\n continue retry;\n // else CAS failed due to workerCount change; retry inner loop\n }\n }\n\n // 前面線程計數增加成功\n boolean workerStarted = false;\n boolean workerAdded = false;\n Worker w = null;\n try {\n // 創建了一個worker,包裝了任務\n w = new Worker(firstTask);\n final Thread t = w.thread;\n // 線程創建成功\n if (t != null) {\n // 獲取鎖\n final ReentrantLock mainLock = this.mainLock;\n mainLock.lock();\n try {\n // 再次確認狀態\n int rs = runStateOf(ctl.get());\n if (rs < SHUTDOWN ||\n (rs == SHUTDOWN && firstTask == null)) {\n // 如果線程已經啓動,失敗\n if (t.isAlive()) // precheck that t is startable\n throw new IllegalThreadStateException();\n // 新增線程到集合\n workers.add(w);\n // 獲取大小\n int s = workers.size();\n // 判斷最大線程池數量\n if (s > largestPoolSize)\n largestPoolSize = s;\n // 已經添加工作線程\n workerAdded = true;\n }\n } finally {\n // 解鎖\n mainLock.unlock();\n }\n // 如果已經添加\n if (workerAdded) {\n // 啓動線程\n t.start();\n workerStarted = true;\n }\n }\n } finally {\n // 如果沒有啓動\n if (! workerStarted)\n // 失敗處理\n addWorkerFailed(w);\n }\n return workerStarted;\n }\n","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":"codeinline","content":[{"type":"text","text":"Worker","attrs":{}}],"attrs":{}},{"type":"text","text":"這個類的時候,我們講解到其實它的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"run()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法調用的是外部的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"runWorker()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法,那麼我們來看看","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"runWorkder()","attrs":{}}],"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":"首先,它會直接處理自己的firstTask,這個任務並沒有在任務隊列裏面,而是它自己持有的:","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 // 當前線程\n Thread wt = Thread.currentThread();\n // 第一個任務\n Runnable task = w.firstTask;\n // 重置爲null\n w.firstTask = null;\n // 允許打斷\n w.unlock();\n boolean completedAbruptly = true;\n try {\n // 任務不爲空,或者獲取的任務不爲空\n while (task != null || (task = getTask()) != null) {\n // 加鎖\n w.lock();\n //如果線程池停止,確保線程被中斷;\n //如果不是,確保線程沒有被中斷。這\n //在第二種情況下需要複查處理\n // shutdown - now競賽同時清除中斷\n if ((runStateAtLeast(ctl.get(), STOP) ||\n (Thread.interrupted() &&\n runStateAtLeast(ctl.get(), STOP))) &&\n !wt.isInterrupted())\n wt.interrupt();\n try {\n // 執行之前回調方法(可以由我們自己實現)\n beforeExecute(wt, task);\n Throwable thrown = null;\n try {\n // 執行任務\n task.run();\n } catch (RuntimeException x) {\n thrown = x; throw x;\n } catch (Error x) {\n thrown = x; throw x;\n } catch (Throwable x) {\n thrown = x; throw new Error(x);\n } finally {\n // 執行之後回調方法\n afterExecute(task, thrown);\n }\n } finally {\n // 置爲null\n task = null;\n // 更新完成任務\n w.completedTasks++;\n w.unlock();\n }\n }\n // 完成\n completedAbruptly = false;\n } finally {\n // 處理線程退出相關工作\n processWorkerExit(w, completedAbruptly);\n }\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":"上面可以看到如果當前的任務是null,會去獲取一個task,我們看看","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getTask()","attrs":{}}],"attrs":{}},{"type":"text","text":",裏面涉及到了兩個參數,一個是是不是允許核心線程銷燬,另外一個是線程數是不是大於核心線程數,如果滿足條件,就從隊列中取出任務,如果超時取不到,那就返回空,表示沒有取到任務,沒有取到任務,就不會執行前面的循環,就會觸發線程銷燬","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"processWorkerExit()","attrs":{}}],"attrs":{}},{"type":"text","text":"等工作。","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 // 是否超時\n boolean timedOut = false; // Did the last poll() time out?\n\n for (;;) {\n int c = ctl.get();\n int rs = runStateOf(c);\n\n // SHUTDOWN狀態繼續處理隊列中的任務,但是不接收新的任務\n if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {\n decrementWorkerCount();\n return null;\n }\n // 線程數\n int wc = workerCountOf(c);\n\n // 是否允許核心線程超時或者線程數大於核心線程數\n boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;\n\n if ((wc > maximumPoolSize || (timed && timedOut))\n && (wc > 1 || workQueue.isEmpty())) {\n // 減少線程成功,就返回null,後面由processWorkerExit()處理\n if (compareAndDecrementWorkerCount(c))\n return null;\n continue;\n }\n\n try {\n // 如果允許核心線程關閉,或者超過了核心線程,就可以在超時的時間內獲取任務,或者直接取出任務\n Runnable r = timed ?\n workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :\n workQueue.take();\n // 如果能取到任務,那就肯定可以執行\n if (r != null)\n return r;\n // 否則就獲取不到任務,超時了\n timedOut = true;\n } catch (InterruptedException retry) {\n timedOut = false;\n }\n }\n}\n","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":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" private void processWorkerExit(Worker w, boolean completedAbruptly) {\n // 如果是突然結束的線程,那麼之前的線程數是沒有調整的,這裏需要調整\n if (completedAbruptly)\n decrementWorkerCount();\n // 獲取鎖\n final ReentrantLock mainLock = this.mainLock;\n mainLock.lock();\n \n try {\n // 完成的任務數\n completedTaskCount += w.completedTasks;\n // 移除線程\n workers.remove(w);\n } finally {\n // 解鎖\n mainLock.unlock();\n }\n // 試圖停止\n tryTerminate();\n // 獲取狀態\n int c = ctl.get();\n // 比stop小,至少是shutdown\n if (runStateLessThan(c, STOP)) {\n // 如果不是突然完成\n if (!completedAbruptly) {\n // 最小值要麼是0,要麼是核心線程數,要是允許核心線程超時銷燬,那麼就是0\n int min = allowCoreThreadTimeOut ? 0 : corePoolSize;\n // 如果最小的是0或者隊列不是空的,那麼保留一個線程\n if (min == 0 && ! workQueue.isEmpty())\n min = 1;\n // 只要大於等於最小的線程數,就結束當前線程\n if (workerCountOf(c) >= min)\n return; // replacement not needed\n }\n // 否則的話,可能還需要新增工作線程\n addWorker(null, false);\n }\n }\n","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":"codeinline","content":[{"type":"text","text":"shutdown()","attrs":{}}],"attrs":{}},{"type":"text","text":"或者","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"shutdownNow()","attrs":{}}],"attrs":{}},{"type":"text","text":",","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"shutdown()","attrs":{}}],"attrs":{}},{"type":"text","text":"可以繼續處理隊列中的任務,而","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"shutdownNow()","attrs":{}}],"attrs":{}},{"type":"text","text":"會立即清理任務,並返回未執行的任務。","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 // 獲取鎖\n final ReentrantLock mainLock = this.mainLock;\n mainLock.lock();\n try {\n // 檢查停止權限\n checkShutdownAccess();\n // 更新狀態\n advanceRunState(SHUTDOWN);\n // 中斷所有線程\n interruptIdleWorkers();\n // 回調鉤子\n onShutdown(); // hook for ScheduledThreadPoolExecutor\n } finally {\n mainLock.unlock();\n }\n tryTerminate();\n }\n // 立刻停止\n public List shutdownNow() {\n List tasks;\n // 獲取鎖\n final ReentrantLock mainLock = this.mainLock;\n mainLock.lock();\n try {\n // 檢查停止權限\n checkShutdownAccess();\n // 更新狀態到stop\n advanceRunState(STOP);\n // 中斷所有線程\n interruptWorkers();\n // 清理隊列\n tasks = drainQueue();\n } finally {\n mainLock.unlock();\n }\n tryTerminate();\n // 返回任務列表(未完成)\n return tasks;\n }\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"execute()和submit()方法","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"execute() ","attrs":{}}],"attrs":{}},{"type":"text","text":"方法可以提交不需要返回值的任務,無法判斷任務是否被線程池執行是否成功","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"submit()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法用於提交需要返回值的任務。線程池會返回一個future類型的對象,通過這個對象,我們調用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"get()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法就可以","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"阻塞","attrs":{}},{"type":"text","text":",直到獲取到線程執行完成的結果,同時我們也可以使用有超時時間的等待方法","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"get(long timeout,TimeUnit unit)","attrs":{}}],"attrs":{}},{"type":"text","text":",這樣不管線程有沒有執行完成,如果到時間,也不會阻塞,直接返回null。返回的是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"RunnableFuture","attrs":{}}],"attrs":{}},{"type":"text","text":"對象,繼承了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Runnable, Future","attrs":{}}],"attrs":{}},{"type":"text","text":"兩個接口:","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public interface RunnableFuture extends Runnable, Future {\n /**\n * Sets this Future to the result of its computation\n * unless it has been cancelled.\n */\n void run();\n}\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"線程池爲什麼使用阻塞隊列?","attrs":{}}]},{"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":"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":"codeinline","content":[{"type":"text","text":"offer(E o, long timeout, TimeUnit unit)","attrs":{}}],"attrs":{}},{"type":"text","text":"設定等待的時間,如果在指定的時間內,還不能往隊列中加入","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"BlockingQueue","attrs":{}}],"attrs":{}},{"type":"text","text":",則返回失敗,也可以使用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"put(Object)","attrs":{}}],"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":"如果消費速度快,生產者來不及生產,獲取任務的時候,可以使用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"poll(time)","attrs":{}}],"attrs":{}},{"type":"text","text":",有數據則直接取出來,沒數據則可以等待","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"time","attrs":{}}],"attrs":{}},{"type":"text","text":"時間後,返回","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"null","attrs":{}}],"attrs":{}},{"type":"text","text":"。也可以使用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"take()","attrs":{}}],"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":"上面說了阻塞隊列的屬性,那麼爲啥要用呢?","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":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"常見的阻塞隊列","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0b/0b3e290a52869e3e8c1298cf7befe739.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"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":"ArrayBlockingQueue","attrs":{}},{"type":"text","text":":基於數組實現,內部有一個定長的數組,同時保存着隊列頭和尾部的位置。","attrs":{}}]}]},{"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":"LinkedBlockingQueue","attrs":{}},{"type":"text","text":":基於鏈表的阻塞對壘,生產者和消費者使用獨立的鎖,並行能力強,如果不指定容量,默認是無效容量,容易系統內存耗盡。","attrs":{}}]}]},{"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":"DelayQueue","attrs":{}},{"type":"text","text":":延遲隊列,沒有大小限制,生產數據不會被阻塞,消費數據會,只有指定的延遲時間到了,才能從隊列中獲取到該元素。","attrs":{}}]}]},{"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":"PriorityBlockingQueue","attrs":{}},{"type":"text","text":":基於優先級的阻塞隊列,按照優先級進行消費,內部控制同步的是公平鎖。","attrs":{}}]}]},{"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":"SynchronousQueue","attrs":{}},{"type":"text","text":":沒有緩衝,生產者直接把任務交給消費者,少了中間的緩存區。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"線程池如何複用線程的?執行完成的線程怎麼處理","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面的源碼分析,其實已經講解過這個問題了,線程池的線程調用的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"run()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法,其實調用的是","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"runWorker()","attrs":{}}],"attrs":{}},{"type":"text","text":",裏面是死循環,除非獲取不到任務,如果沒有了任務firstTask並且從任務隊列中獲取不到任務,超時的時候,會再判斷是不是可以銷燬核心線程,或者超過了核心線程數,滿足條件的時候,纔會讓當前的線程結束。","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":"codeinline","content":[{"type":"text","text":"start()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法只能調用一次,因此調用到","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"run()","attrs":{}}],"attrs":{}},{"type":"text","text":"方法的時候,調用外面的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"runWorker()","attrs":{}}],"attrs":{}},{"type":"text","text":",讓其在","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"runWorker()","attrs":{}}],"attrs":{}},{"type":"text","text":"的時候,不斷的循環,獲取任務。獲取到任務,調用任務的","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"run()","attrs":{}}],"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":"執行完成的線程會調用","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"processWorkerExit()","attrs":{}}],"attrs":{}},{"type":"text","text":",前面有分析,裏面會獲取鎖,把線程數減少,從工作線程從集合中移除,移除掉之後,會判斷線程是不是太少了,如果是,會再加回來,個人以爲是一種補救。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"如何配置線程池參數?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般而言,有個公式,如果是計算(CPU)密集型的任務,那麼核心線程數設置爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"處理器核數-1","attrs":{}}],"attrs":{}},{"type":"text","text":",如果是io密集型(很多網絡請求),那麼就可以設置爲","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"2*處理器核數","attrs":{}}],"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":"一般 8 核的機器,設置 10-12 個核心線程就差不多了,這一切必須按照業務具體值進行計算。設置過多的線程數,上下文切換,競爭激烈,設置過少,沒有辦法充分利用計算機的資源。","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","text":"計算(CPU)密集型消耗的主要是 CPU 資源,可以將線程數設置爲 N(CPU 核心數)+1,比 CPU 核心數多出來的一個線程是爲了防止線程偶發的缺頁中斷,或者其它原因導致的任務暫停而帶來的影響。一旦任務暫停,CPU 就會處於空閒狀態,而在這種情況下多出來的一個線程就可以充分利用 CPU 的空閒時間。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"io密集型系統會用大部分的時間來處理 I/O 交互,而線程在處理 I/O 的時間段內不會佔用 CPU 來處理,這時就可以將 CPU 交出給其它線程使用。因此在 I/O 密集型任務的應用中,我們可以多配置一些線程,具體的計算方法是 2N。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"爲什麼不推薦默認的線程池創建方式?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阿里的編程規範裏面,不建議使用默認的方式來創建線程,是因爲這樣創建出來的線程很多時候參數都是默認的,可能創建者不太瞭解,很容易出問題,最好通過","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"new ThreadPoolExecutor()","attrs":{}}],"attrs":{}},{"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":"Executors.newFixedThreadPool():無界隊列,內存可能被打爆","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Executors.newSingleThreadExecutor():單個線程,效率低,串行。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Executors.newCachedThreadPool():沒有核心線程,最大線程數可能爲無限大,內存可能還會爆掉。","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":"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":"任務隊列怎麼設置?","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":"最大線程數多少?","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":"創建線程的時候沒有名稱,追溯問題不好找。","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"線程池的拒絕策略","attrs":{}}]},{"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":"image","attrs":{"src":"https://static001.geekbang.org/infoq/28/28f00a98a7f56844f2ff93a1640736b6.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"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":"AbortPolicy: 不執行新的任務,直接拋出異常,提示線程池已滿","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"DisCardPolicy:不執行新的任務,但是也不會拋出異常,默默的","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"DisCardOldSetPolicy:丟棄消息隊列中最老的任務,變成新進來的任務","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CallerRunsPolicy:直接調用當前的execute來執行任務","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":"一般而言,上面的拒絕策略都不會特別理想,一般要是任務滿了,首先需要做的就是看任務是不是必要的,如果非必要,非核心,可以考慮拒絕掉,並報錯提醒,如果是必須的,必須把它保存起來,不管是使用mq消息,還是其他手段,不能丟任務。在這些過程中,日誌是非常必要的。既要保護線程池,也要對業務負責。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"線程池監控與動態調整","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程池提供了一些API,可以動態獲取線程池的狀態,並且還可以設置線程池的參數,以及狀態:","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":"image","attrs":{"src":"https://static001.geekbang.org/infoq/af/af7dedc627c677e378981cb613543582.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"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":"image","attrs":{"src":"https://static001.geekbang.org/infoq/74/743a317802323ca86f8ab2105491ad66.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"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":"關於這一點,美團的線程池文章講得很清楚,甚至做了一個實時調整線程池參數的平臺,可以進行跟蹤監控,線程池活躍度、任務的執行Transaction(頻率、耗時)、Reject異常、線程池內部統計信息等等。這裏我就不展開了,原文:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html ,這是我們可以參考的思路。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"線程池隔離","attrs":{}}]},{"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":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Hystrix","attrs":{}}],"attrs":{}},{"type":"text","text":"裏面運用到這個技術,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Hystrix","attrs":{}}],"attrs":{}},{"type":"text","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":"秦懷,公衆號【","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"秦懷雜貨店","attrs":{}},{"type":"text","text":"】作者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。個人寫作方向:Java源碼解析,JDBC,Mybatis,Spring,redis,分佈式,劍指Offer,LeetCode等,認真寫好每一篇文章,不喜歡標題黨,不喜歡花裏胡哨,大多寫系列文章,不能保證我寫的都完全正確,但是我保證所寫的均經過實踐或者查找資料。遺漏或者錯誤之處,還望指正。","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":"link","attrs":{"href":"http://aphysia.cn/archives/2020","title":"","type":null},"content":[{"type":"text","text":"2020年我寫了什麼?","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":"link","attrs":{"href":"https://damaer.github.io/Coding/#/","title":"","type":null},"content":[{"type":"text","text":"開源編程筆記","attrs":{}}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章