<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                線程池是為了避免線程頻繁的創建和銷毀帶來的性能消耗,而建立的一種池化技術,它是把已創建的線程放入“池”中,當有任務來臨時就可以重用已有的線程,無需等待創建的過程,這樣就可以有效提高程序的響應速度。但如果要說線程池的話一定離不開 ThreadPoolExecutor ,在阿里巴巴的《Java 開發手冊》中是這樣規定線程池的: 線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的讀者更加明確線程池的運行規則,規避資源耗盡的風險。 說明:Executors 返回的線程池對象的弊端如下: * FixedThreadPool 和 SingleThreadPool:允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。 * CachedThreadPool 和 ScheduledThreadPool:允許的創建線程數量為 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。 其實當我們去看 Executors 的源碼會發現,Executors.newFixedThreadPool()、Executors.newSingleThreadExecutor() 和 Executors.newCachedThreadPool() 等方法的底層都是通過 ThreadPoolExecutor 實現的,所以本課時我們就重點來了解一下 ThreadPoolExecutor 的相關知識,比如它有哪些核心的參數?它是如何工作的? #### 典型回答 ThreadPoolExecutor 的核心參數指的是它在構建時需要傳遞的參數,其構造方法如下所示: ``` public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || // maximumPoolSize 必須大于 0,且必須大于 corePoolSize maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; } ``` * 第 1 個參數:corePoolSize 表示線程池的常駐核心線程數。如果設置為 0,則表示在沒有任何任務時,銷毀線程池;如果大于 0,即使沒有任務時也會保證線程池的線程數量等于此值。但需要注意,此值如果設置的比較小,則會頻繁的創建和銷毀線程(創建和銷毀的原因會在本課時的下半部分講到);如果設置的比較大,則會浪費系統資源,所以開發者需要根據自己的實際業務來調整此值。 * 第 2 個參數:maximumPoolSize 表示線程池在任務最多時,最大可以創建的線程數。官方規定此值必須大于 0,也必須大于等于 corePoolSize,此值只有在任務比較多,且不能存放在任務隊列時,才會用到。 * 第 3 個參數:keepAliveTime 表示線程的存活時間,當線程池空閑時并且超過了此時間,多余的線程就會銷毀,直到線程池中的線程數量銷毀的等于 corePoolSize 為止,如果 maximumPoolSize 等于 corePoolSize,那么線程池在空閑的時候也不會銷毀任何線程。 * 第 4 個參數:unit 表示存活時間的單位,它是配合 keepAliveTime 參數共同使用的。 * 第 5 個參數:workQueue 表示線程池執行的任務隊列,當線程池的所有線程都在處理任務時,如果來了新任務就會緩存到此任務隊列中排隊等待執行。 * 第 6 個參數:threadFactory 表示線程的創建工廠,此參數一般用的比較少,我們通常在創建線程池時不指定此參數,它會使用默認的線程創建工廠的方法來創建線程,源代碼如下: ``` public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { // Executors.defaultThreadFactory() 為默認的線程創建工廠 this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); } public static ThreadFactory defaultThreadFactory() { return new DefaultThreadFactory(); } // 默認的線程創建工廠,需要實現 ThreadFactory 接口 static class DefaultThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1); private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; DefaultThreadFactory() { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; } // 創建線程 public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); if (t.isDaemon()) t.setDaemon(false); // 創建一個非守護線程 if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); // 線程優先級設置為默認值 return t; } } ``` 我們也可以自定義一個線程工廠,通過實現 ThreadFactory 接口來完成,這樣就可以自定義線程的名稱或線程執行的優先級了。 * 第 7 個參數:RejectedExecutionHandler 表示指定線程池的拒絕策略,當線程池的任務已經在緩存隊列 workQueue 中存儲滿了之后,并且不能創建新的線程來執行此任務時,就會用到此拒絕策略,它屬于一種限流保護的機制。 線程池的工作流程要從它的執行方法 execute() 說起,源碼如下: ``` public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); // 當前工作的線程數小于核心線程數 if (workerCountOf(c) < corePoolSize) { // 創建新的線程執行此任務 if (addWorker(command, true)) return; c = ctl.get(); } // 檢查線程池是否處于運行狀態,如果是則把任務添加到隊列 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); // 再出檢查線程池是否處于運行狀態,防止在第一次校驗通過后線程池關閉 // 如果是非運行狀態,則將剛加入隊列的任務移除 if (! isRunning(recheck) && remove(command)) reject(command); // 如果線程池的線程數為 0 時(當 corePoolSize 設置為 0 時會發生) else if (workerCountOf(recheck) == 0) addWorker(null, false); // 新建線程執行任務 } // 核心線程都在忙且隊列都已爆滿,嘗試新啟動一個線程執行失敗 else if (!addWorker(command, false)) // 執行拒絕策略 reject(command); } ``` 其中 addWorker(Runnable firstTask, boolean core) 方法的參數說明如下: * firstTask,線程應首先運行的任務,如果沒有則可以設置為 null; * core,判斷是否可以創建線程的閥值(最大值),如果等于 true 則表示使用 corePoolSize 作為閥值,false 則表示使用 maximumPoolSize 作為閥值。 #### 考點分析 本課時的這道面試題考察的是你對于線程池和 ThreadPoolExecutor 的掌握程度,也屬于 Java 的基礎知識,幾乎所有的面試都會被問到,其中線程池任務執行的主要流程,可以參考以下流程圖: ![](https://img.kancloud.cn/2d/6c/2d6c7345bc1e2a1ec73f4e1427b38f65_407x650.png) 與 ThreadPoolExecutor 相關的面試題還有以下幾個: * ThreadPoolExecutor 的執行方法有幾種?它們有什么區別? * 什么是線程的拒絕策略? * 拒絕策略的分類有哪些? * 如何自定義拒絕策略? * ThreadPoolExecutor 能不能實現擴展?如何實現擴展? #### 知識擴展 * [ ] execute() VS submit() execute() 和 submit() 都是用來執行線程池任務的,它們最主要的區別是,submit() 方法可以接收線程池執行的返回值,而 execute() 不能接收返回值。 來看兩個方法的具體使用: ``` ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue(20)); // execute 使用 executor.execute(new Runnable() { @Override public void run() { System.out.println("Hello, execute."); } }); // submit 使用 Future<String> future = executor.submit(new Callable<String>() { @Override public String call() throws Exception { System.out.println("Hello, submit."); return "Success"; } }); System.out.println(future.get()); ``` 以上程序執行結果如下: ``` Hello, submit. Hello, execute. Success ``` 從以上結果可以看出 submit() 方法可以配合 Futrue 來接收線程執行的返回值。它們的另一個區別是 execute() 方法屬于 Executor 接口的方法,而 submit() 方法則是屬于 ExecutorService 接口的方法,它們的繼承關系如下圖所示: ![](https://img.kancloud.cn/9a/be/9abe2b33e9d1e19adf6e8d83dedbcd53_390x447.png) * [ ] 線程池的拒絕策略 當線程池中的任務隊列已經被存滿,再有任務添加時會先判斷當前線程池中的線程數是否大于等于線程池的最大值,如果是,則會觸發線程池的拒絕策略。 Java 自帶的拒絕策略有 4 種: * AbortPolicy,終止策略,線程池會拋出異常并終止執行,它是默認的拒絕策略; * CallerRunsPolicy,把任務交給當前線程來執行; * DiscardPolicy,忽略此任務(最新的任務); * DiscardOldestPolicy,忽略最早的任務(最先加入隊列的任務)。 例如,我們來演示一個 AbortPolicy 的拒絕策略,代碼如下: ``` ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 3, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2), new ThreadPoolExecutor.AbortPolicy()); // 添加 AbortPolicy 拒絕策略 for (int i = 0; i < 6; i++) { executor.execute(() -> { System.out.println(Thread.currentThread().getName()); }); } ``` 以上程序的執行結果: ``` pool-1-thread-1 pool-1-thread-1 pool-1-thread-1 pool-1-thread-3 pool-1-thread-2 Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.lagou.interview.ThreadPoolExample$$Lambda$1/1096979270@448139f0 rejected from java.util.concurrent.ThreadPoolExecutor@7cca494b[Running, pool size = 3, active threads = 3, queued tasks = 2, completed tasks = 0] at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379) at com.lagou.interview.ThreadPoolExample.rejected(ThreadPoolExample.java:35) at com.lagou.interview.ThreadPoolExample.main(ThreadPoolExample.java:26) ``` 可以看出當第 6 個任務來的時候,線程池則執行了 AbortPolicy 拒絕策略,拋出了異常。因為隊列最多存儲 2 個任務,最大可以創建 3 個線程來執行任務(2+3=5),所以當第 6 個任務來的時候,此線程池就“忙”不過來了。 自定義拒絕策略 自定義拒絕策略只需要新建一個 RejectedExecutionHandler 對象,然后重寫它的 rejectedExecution() 方法即可,如下代碼所示: ``` ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 3, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2), new RejectedExecutionHandler() { // 添加自定義拒絕策略 @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 業務處理方法 System.out.println("執行自定義拒絕策略"); } }); for (int i = 0; i < 6; i++) { executor.execute(() -> { System.out.println(Thread.currentThread().getName()); }); } ``` 以上代碼執行的結果如下: ``` 執行自定義拒絕策略 pool-1-thread-2 pool-1-thread-3 pool-1-thread-1 pool-1-thread-1 pool-1-thread-2 ``` 可以看出線程池執行了自定義的拒絕策略,我們可以在 rejectedExecution 中添加自己業務處理的代碼。 ThreadPoolExecutor 擴展 ThreadPoolExecutor 的擴展主要是通過重寫它的 beforeExecute() 和 afterExecute() 方法實現的,我們可以在擴展方法中添加日志或者實現數據統計,比如統計線程的執行時間,如下代碼所示: ``` public class ThreadPoolExtend { public static void main(String[] args) throws ExecutionException, InterruptedException { // 線程池擴展調用 MyThreadPoolExecutor executor = new MyThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new LinkedBlockingQueue()); for (int i = 0; i < 3; i++) { executor.execute(() -> { Thread.currentThread().getName(); }); } } /** * 線程池擴展 */ static class MyThreadPoolExecutor extends ThreadPoolExecutor { // 保存線程執行開始時間 private final ThreadLocal<Long> localTime = new ThreadLocal<>(); public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } /** * 開始執行之前 * @param t 線程 * @param r 任務 */ @Override protected void beforeExecute(Thread t, Runnable r) { Long sTime = System.nanoTime(); // 開始時間 (單位:納秒) localTime.set(sTime); System.out.println(String.format("%s | before | time=%s", t.getName(), sTime)); super.beforeExecute(t, r); } /** * 執行完成之后 * @param r 任務 * @param t 拋出的異常 */ @Override protected void afterExecute(Runnable r, Throwable t) { Long eTime = System.nanoTime(); // 結束時間 (單位:納秒) Long totalTime = eTime - localTime.get(); // 執行總時間 System.out.println(String.format("%s | after | time=%s | 耗時:%s 毫秒", Thread.currentThread().getName(), eTime, (totalTime / 1000000.0))); super.afterExecute(r, t); } } } ``` 以上程序的執行結果如下所示: ``` pool-1-thread-1 | before | time=4570298843700 pool-1-thread-2 | before | time=4570298840000 pool-1-thread-1 | after | time=4570327059500 | 耗時:28.2158 毫秒 pool-1-thread-2 | after | time=4570327138100 | 耗時:28.2981 毫秒 pool-1-thread-1 | before | time=4570328467800 pool-1-thread-1 | after | time=4570328636800 | 耗時:0.169 毫秒 ``` #### 小結 最后我們總結一下:線程池的使用必須要通過 ThreadPoolExecutor 的方式來創建,這樣才可以更加明確線程池的運行規則,規避資源耗盡的風險。同時,也介紹了 ThreadPoolExecutor 的七大核心參數,包括核心線程數和最大線程數之間的區別,當線程池的任務隊列沒有可用空間且線程池的線程數量已經達到了最大線程數時,則會執行拒絕策略,Java 自動的拒絕策略有 4 種,用戶也可以通過重寫 rejectedExecution() 來自定義拒絕策略,我們還可以通過重寫 beforeExecute() 和 afterExecute() 來實現 ThreadPoolExecutor 的擴展功能。 #### 課后問答 * 1、核心線程數設置的過小會頻繁創建和銷毀線程,說會在下半部分解答,沒有發現呀 講師回復: 就是參數說明部分,可以看下哦。 * 2、AbortPolicy,終止策略,線程池會拋出異常并終止執行,它是默認的拒絕策略;是整個線程池都終止嗎?包括正常執行的線程? 講師回復: 不是的,文中有寫,只有達到線程的終止條件之后才會觸發線程池的終止策略。 * 3、想向您請教一下非核心線程的銷毀方式,這塊不是很明白,是通過中斷回收的嗎? 講師回復: 非核心線程會在線程池空閑的時候回收 * 4、表示線程池的常駐核心線程數。如果設置為 0,則表示在沒有任何任務時,銷毀線程池。銷毀線程池是什么意思? 講師回復: 就是注銷所有的線程
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看