如何使用executors創建線程池?1.使用newfixedthreadpool(int nthreads)創建固定大小的線程池;2.使用newcachedthreadpool()創建可緩存線程池;3.使用newsinglethreadexecutor()創建單線程線程池;4.使用newscheduledthreadpool(int corepoolsize)創建支持定時和周期任務的線程池。線程池的拒絕策略是什么?如何自定義拒絕策略?默認使用abortpolicy,其他內置策略包括callerrunspolicy、discardpolicy和discardoldestpolicy,自定義需實現rejectedexecutionhandler接口并在創建線程池時傳入。如何優雅地關閉線程池?調用shutdown()平滑關閉,調用shutdownnow()立即停止任務,結合awaittermination()等待任務完成或超時處理。如何監控線程池的狀態?通過getpoolsize()、getactivecount()、getcompletedtaskcount()、gettaskcount()和getqueue().size()獲取狀態信息。executors創建的線程池有什么缺點?其隊列容量過大可能導致oom,newcachedthreadpool可能創建過多線程耗盡資源,推薦手動使用threadpoolexecutor創建以精確控制參數并避免風險。
Executors類是Java并發包中一個強大的工具類,它簡化了線程池的創建和管理,提供了一系列靜態工廠方法,幫助開發者快速構建各種類型的線程池,而無需深入了解線程池的底層細節。
Executors類主要用于創建不同類型的線程池,它隱藏了線程池的復雜性,使開發者能夠更專注于任務的執行。
如何使用Executors創建線程池?
Executors類提供了多種靜態工廠方法,用于創建不同類型的線程池,以下是一些常用的方法:
立即學習“Java免費學習筆記(深入)”;
-
newFixedThreadPool(int nThreads): 創建一個固定大小的線程池,池中始終保持指定數量的線程。如果所有線程都在忙碌,新的任務將被放入隊列中等待。
ExecutorService executor = Executors.newFixedThreadPool(10); for (int i = 0; i < 100; i++) { executor.execute(new Task(i)); } executor.shutdown();
-
newCachedThreadPool(): 創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需的數量,會自動回收空閑線程。當有新任務提交時,如果沒有空閑線程可用,則會創建新的線程。這種線程池非常適合處理大量的短期異步任務。
ExecutorService executor = Executors.newCachedThreadPool(); for (int i = 0; i < 100; i++) { executor.execute(new Task(i)); } executor.shutdown();
-
newSingleThreadExecutor(): 創建一個單線程的線程池。所有提交的任務都會按照先進先出的順序執行。如果線程在執行任務時發生異常,會創建一個新的線程來替代它,保證所有任務都會被執行。
ExecutorService executor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 100; i++) { executor.execute(new Task(i)); } executor.shutdown();
-
newScheduledThreadPool(int corePoolSize): 創建一個可以執行定時任務和周期性任務的線程池。
ScheduledExecutorService executor = Executors.newScheduledThreadPool(5); executor.scheduleAtFixedRate(new Task(1), 1, 3, TimeUnit.SECONDS); // 每隔3秒執行一次,延遲1秒后開始執行
線程池的拒絕策略是什么?如何自定義拒絕策略?
當線程池的任務隊列已滿,且線程池中的所有線程都在忙碌時,如果再有新的任務提交,線程池會使用拒絕策略來處理這些任務。Executors創建的線程池默認使用ThreadPoolExecutor.AbortPolicy,該策略會直接拋出RejectedExecutionException異常。
Java提供了四種內置的拒絕策略:
- AbortPolicy: 默認策略,直接拋出RejectedExecutionException異常。
- CallerRunsPolicy: 由提交任務的線程來執行該任務。
- DiscardPolicy: 直接丟棄該任務,不拋出異常。
- DiscardOldestPolicy: 丟棄隊列中最老的未處理任務,然后嘗試執行當前任務。
當然,你也可以自定義拒絕策略,只需要實現RejectedExecutionHandler接口即可:
import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; public class CustomRejectedExecutionHandler implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("Task " + r.toString() + " rejected from " + executor.toString()); // 可以添加自定義的拒絕處理邏輯,例如記錄日志、持久化任務等 } }
然后,在創建線程池時,將自定義的拒絕策略傳遞給ThreadPoolExecutor的構造函數:
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class Main { public static void main(String[] args) { int corePoolSize = 5; int maxPoolSize = 10; long keepAliveTime = 5000; ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(100), // 任務隊列 new CustomRejectedExecutionHandler() // 自定義拒絕策略 ); for (int i = 0; i < 200; i++) { executor.execute(new Task(i)); } executor.shutdown(); } }
如何優雅地關閉線程池?shutdown()和shutdownNow()有什么區別?
關閉線程池是一個重要的步驟,它可以防止資源泄漏。Executors創建的線程池提供了兩種關閉方法:shutdown()和shutdownNow()。
-
shutdown(): 平滑關閉線程池。它會阻止新的任務提交,但會等待所有已提交的任務執行完成。
-
shutdownNow(): 立即關閉線程池。它會嘗試停止所有正在執行的任務,并返回一個包含所有等待執行的任務的列表。需要注意的是,shutdownNow()方法并不能保證所有任務都會被立即停止,因為某些任務可能正在執行一些無法中斷的操作。
在調用shutdown()或shutdownNow()方法后,可以使用awaitTermination()方法來等待線程池中的所有任務執行完成,或者等待指定的超時時間。
ExecutorService executor = Executors.newFixedThreadPool(10); // 提交任務... executor.shutdown(); // 阻止新的任務提交 try { if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { // 等待60秒 executor.shutdownNow(); // 如果超時,則嘗試立即停止所有任務 } } catch (InterruptedException e) { executor.shutdownNow(); // 如果當前線程被中斷,則嘗試立即停止所有任務 }
如何監控線程池的狀態?
監控線程池的狀態對于診斷性能問題和優化線程池配置非常重要。ThreadPoolExecutor類提供了一些方法來獲取線程池的狀態信息:
- getPoolSize(): 返回線程池中的線程數量。
- getActiveCount(): 返回正在執行任務的線程數量。
- getCompletedTaskCount(): 返回已完成的任務數量。
- getTaskCount(): 返回已提交的任務總數。
- getQueue().size(): 返回任務隊列中的任務數量。
你可以使用這些方法來定期監控線程池的狀態,并根據需要調整線程池的配置。例如,如果getQueue().size()持續增長,說明任務提交的速度超過了線程池的處理能力,可能需要增加線程池的大小。
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10); // 提交任務... while (true) { System.out.println("Pool Size: " + executor.getPoolSize()); System.out.println("Active Count: " + executor.getActiveCount()); System.out.println("Completed Task Count: " + executor.getCompletedTaskCount()); System.out.println("Task Count: " + executor.getTaskCount()); System.out.println("Queue Size: " + executor.getQueue().size()); Thread.sleep(1000); // 每隔1秒打印一次狀態信息 }
Executors創建的線程池有什么缺點?為什么推薦使用ThreadPoolExecutor手動創建?
雖然Executors類簡化了線程池的創建,但它也存在一些缺點:
-
newFixedThreadPool()和newSingleThreadExecutor(): 它們的任務隊列使用了LinkedBlockingQueue,該隊列的默認容量是Integer.MAX_VALUE。如果任務提交的速度超過了線程池的處理能力,會導致大量的任務堆積在隊列中,最終可能導致OOM(OutOfMemoryError)異常。
-
newCachedThreadPool(): 它可以創建無限數量的線程,如果任務提交的速度過快,可能會創建大量的線程,導致系統資源耗盡。
因此,阿里巴巴Java開發手冊中建議使用ThreadPoolExecutor手動創建線程池,而不是使用Executors類。手動創建線程池可以更精確地控制線程池的參數,例如核心線程數、最大線程數、任務隊列的類型和容量、拒絕策略等,從而避免潛在的風險。
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class Main { public static void main(String[] args) { int corePoolSize = 5; int maxPoolSize = 10; long keepAliveTime = 5000; ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(100) // 任務隊列,設置容量 ); for (int i = 0; i < 200; i++) { executor.execute(new Task(i)); } executor.shutdown(); } }
總之,Executors類是Java并發編程中一個非常有用的工具,但需要了解其潛在的風險,并根據實際情況選擇合適的線程池創建方式。在大多數情況下,推薦使用ThreadPoolExecutor手動創建線程池,以便更好地控制線程池的參數,并避免潛在的OOM風險。