一、使用線程池的目的:處理異步任務(雖然有同步線程池SyncTaskExecutor,但是本質不算一個線程池,只有同步操作,沒有異步調用)
二、同步與異步區別
同步:同步就是整個處理過程順序執行,當各個過程都執行完畢,並返回結果。
異步:異步調用則是只是發送了調用的指令,調用者無需等待被調用的方法完全執行完畢;而是繼續執行下面的流程。例如, 在某個調用中,需要順序調用 A, B, C三個過程方法;如他們都是同步調用,則需要將他們都順序執行完畢之后,方算作過程執行完畢;如B為一個異步的調用方法,則在執行完A之后,調用B,並不等待B完成,而是執行開始調用C,待C執行完畢之后,就意味着這個過程執行完畢了。在Java中,一般在處理類似的場景之時,都是基於創建獨立的線程去完成相應的異步調用邏輯,通過主線程和不同的業務子線程之間的執行流程,從而在啟動獨立的線程之后,主線程繼續執行而不會產生停滯等待的情況。
三、異步任務使用:@EnableAsync @Async("線程池名")
1. 注意事項:
a. 在Spring啟動類上添加注解@EnableAsyn或者在你們線程池配置類添加@EnableAsyn(任選其一即可)
b. @Async不指定value則使用默認異步線程池
c. 使用此注解的方法的類對象,需要是spring管理下的bean對象(@Component)
2. Spring默認的異步線程池通過實現AsyncConfigurer接口進行配置
public interface AsyncConfigurer { Executor getAsyncExecutor(); AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler(); }
Executor : 處理異步方法調用時要使用的實例,
AsyncUncaughtExceptionHandler :在使用void
返回類型的異步方法執行期間拋出異常時要使用的實例。
3. 實現舉例:
@Configuration public class ThreadPoolConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor(); // 設置核心線程數 threadPool.setCorePoolSize(100); // 設置最大線程數 threadPool.setMaxPoolSize(200); // 線程池所使用的緩沖隊列 threadPool.setQueueCapacity(50); threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); // 等待任務在關機時完成--表明等待所有線程執行完 threadPool.setWaitForTasksToCompleteOnShutdown(true); // 等待時間 (默認為0,此時立即停止),並沒等待xx秒后強制停止 threadPool.setAwaitTerminationSeconds(60); // 線程名稱前綴 threadPool.setThreadNamePrefix("Derry-Async-"); // 初始化線程 threadPool.initialize(); return threadPool; } /** * 異步異常處理 * @return */ @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return null; } }
4. Spring 已經實現的線程池
-
SimpleAsyncTaskExecutor:不是真的線程池,這個類不重用線程,默認每次調用都會創建一個新的線程。
-
SyncTaskExecutor:這個類沒有實現異步調用,只是一個同步操作。只適用於不需要多線程的地方。
-
ConcurrentTaskExecutor:Executor的適配類,不推薦使用。如果ThreadPoolTaskExecutor不滿足要求時,才用考慮使用這個類。
-
SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的類。線程池同時被quartz和非quartz使用,才需要使用此類。
-
ThreadPoolTaskExecutor :最常使用,推薦。其實質是對java.util.concurrent.ThreadPoolExecutor的包裝。
5. 自定義線程池:
@Component public class ThreadPool1 { //不指定Bean的name則按照方法名創建Bean注入容器 @Bean(name="myAsync") public Executor myAsync() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //最大線程數 executor.setMaxPoolSize(100); //核心線程數 executor.setCorePoolSize(10); //任務隊列的大小 executor.setQueueCapacity(10); //線程前綴名 executor.setThreadNamePrefix("My-Async-ThreadPool-1-"); //線程存活時間 executor.setKeepAliveSeconds(30); // 等待時間 (默認為0,此時立即停止),並沒等待xx秒后強制停止 executor.setAwaitTerminationSeconds(60); //等待任務在關機時完成--表明等待所有線程執行完 executor.setWaitForTasksToCompleteOnShutdown(true); /** * 拒絕處理策略 * CallerRunsPolicy():交由調用方線程運行,比如 main 線程。 * AbortPolicy():直接拋出異常。 * DiscardPolicy():直接丟棄。 * DiscardOldestPolicy():丟棄隊列中最老的任務。 */ executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy()); //線程初始化 executor.initialize(); return executor; } }
自定義線程池需要給@Async指定value值
6. 使用多個線程池進行不同功能的線程隔離
使用多個線程池,把相同的任務放到同一個線程池中,可以起到隔離的作用,避免有線程出錯時影響到其他線程池,例如只有一個線程池時,有兩種任務,下單,處理圖片,如果線程池被處理圖片的任務占滿,影響下單任務的進行
四、定時任務@EnableScheduling @Scheduled
說明:
a. @Scheduled 任務調度注解,主要用於配置定時任務;springboot默認的調度器線程池大小為 1(也就是說在多個方法上加上@schedule的,多個定時任務默認是加入延時隊列依次同步執行的)
b. 在定時任務的方法上加上@Async就會把該定時任務交由異步線程池執行
配置默認調度器線程池的方法實現SchedulingConfigurer 接口
@Configuration public class ScheduledConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { scheduledTaskRegistrar.setScheduler(setTaskExecutors()); } @Bean(destroyMethod="shutdown") public Executor setTaskExecutors(){ return Executors.newScheduledThreadPool(3); // 3個線程來處理。 } }
五、代碼演示
1. 定時任務演示 (為方便演示線程同步執行關系,調度器線程池大小已經配置為3)
// @Async @Scheduled(fixedDelay = 1000*3) public void test() throws InterruptedException { System.out.println(Thread.currentThread().getName()+"開始執行定時任務"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName()+"定時任務執行完畢"+'\n'); }
運行結果:
pool-1-thread-1開始執行定時任務
pool-1-thread-1定時任務執行完畢
pool-1-thread-2開始執行定時任務
pool-1-thread-2定時任務執行完畢
該方法的定時任務3s執行一次,但是每次執行要睡眠5s,由於未開啟異步所以需要等上一個線程執行完畢,下一個線程才能再次進入該方法。
2.加上@Async(未指定具體線程池,定時任務交由默認異步線程池執行)
@Async @Transactional @Scheduled(fixedDelay = 1000*3) public void test() throws InterruptedException { System.out.println(Thread.currentThread().getName()+"開始執行定時任務"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName()+"定時任務執行完畢"+'\n'); }
運行結果:
Default-Async-ThreadPool-1開始執行定時任務 Default-Async-ThreadPool-2開始執行定時任務 //線程2在線程1運行期間便執行第二次定時任務,此即異步 Default-Async-ThreadPool-1定時任務執行完畢 Default-Async-ThreadPool-3開始執行定時任務 //線程3在線程2運行期間便執行第三次定時任務 Default-Async-ThreadPool-2定時任務執行完畢 Default-Async-ThreadPool-4開始執行定時任務 Default-Async-ThreadPool-3定時任務執行完畢
3. 加上@Async並指定具體線程池
@Async("myAsync") @Transactional @Scheduled(fixedDelay = 1000*3) public void test2() throws InterruptedException { System.out.println(Thread.currentThread().getName()+"開始執行定時任務"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName()+"定時任務執行完畢"+'\n'); }
運行結果:
My-Async-ThreadPool-1-1開始執行定時任務 //線程名稱發生變化,可見自定義線程池是生效的 My-Async-ThreadPool-1-2開始執行定時任務 My-Async-ThreadPool-1-1定時任務執行完畢 My-Async-ThreadPool-1-3開始執行定時任務 My-Async-ThreadPool-1-2定時任務執行完畢 My-Async-ThreadPool-1-4開始執行定時任務 My-Async-ThreadPool-1-3定時任務執行完畢
4.多線程池隔離異步執行不同定時任務
@Async //使用默認異步線程池 @Scheduled(fixedDelay = 1000*3) public void test() throws InterruptedException { System.out.println(Thread.currentThread().getName()+"開始執行定時任務"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName()+"定時任務執行完畢"+'\n'); } @Async("myAsync") //使用自定義異步線程池 @Scheduled(fixedDelay = 1000*3) public void test2() throws InterruptedException { System.out.println(Thread.currentThread().getName()+"開始執行定時任務"); Thread.sleep(5000); System.out.println(Thread.currentThread().getName()+"定時任務執行完畢"+'\n'); }
運行結果:
Default-Async-ThreadPool-1開始執行定時任務 My-Async-ThreadPool-1-1開始執行定時任務 Default-Async-ThreadPool-2開始執行定時任務 My-Async-ThreadPool-1-2開始執行定時任務 Default-Async-ThreadPool-1定時任務執行完畢 My-Async-ThreadPool-1-1定時任務執行完畢 Default-Async-ThreadPool-3開始執行定時任務 My-Async-ThreadPool-1-3開始執行定時任務 Default-Async-ThreadPool-2定時任務執行完畢 Default-Async-ThreadPool-4開始執行定時任務 My-Async-ThreadPool-1-4開始執行定時任務 My-Async-ThreadPool-1-2定時任務執行完畢
六、相關隨筆
1. Spring最常用的線程池類型ThreadPoolTaskExecutor參數原理詳解:https://www.cnblogs.com/JNU-Iot-Longxin/p/15305313.html
2. ThreadPoolTaskExecutor另一種使用方式(不推薦):https://www.cnblogs.com/JNU-Iot-Longxin/p/15305050.html