本文實例為大家分享了SpringBoot實現(xiàn)定時任務和異步調用的具體代碼,供大家參考,具體內容如下
目前成都創(chuàng)新互聯(lián)公司已為上千多家的企業(yè)提供了網站建設、域名、網頁空間、網站托管、企業(yè)網站設計、神木網站維護等服務,公司將堅持客戶導向、應用為本的策略,正道將秉承"和諧、參與、激情"的文化,與客戶和合作伙伴齊心協(xié)力一起成長,共同發(fā)展。
環(huán)境:
jdk1.8;spring boot2.0.2;Maven3.3
摘要說明:
定時任務:定時任務是業(yè)務場景中經常出現(xiàn)的一種情況如:定時發(fā)送郵件,短信、定時統(tǒng)計監(jiān)控數據、定時對賬等
異步調用:一個都買流程可能包括下單、發(fā)貨通知、短信推送、消息推送等,其實除了下單這個主要程序是主程序,其他子程序可以同時進行且不影響主程序的運行,這個時候就可以使用異步調用來調用這些子程序;
步驟:
1.定時任務
a.在spring boot主類上使用注解@EnableScheduling啟動定時任務:
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; //啟動定時任務 @EnableScheduling @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
b.實現(xiàn)定時任務(使用@Component注解來標注組件)
/** * @模塊名:demo * @包名:com.example.demo.test1.component * @描述:SchedulingComponent.java * @版本:1.0 * @創(chuàng)建人:cc * @創(chuàng)建時間:2018年9月29日上午10:19:37 */ package com.example.demo.test1.component; import java.util.Date; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** * @模塊名:demo * @包名:com.example.demo.test1.component @類名稱: SchedulingComponent * @類描述:【類描述】用于測試定時任務 @版本:1.0 * @創(chuàng)建人:cc * @創(chuàng)建時間:2018年9月29日上午10:19:37 */ @Component public class SchedulingComponent { /** * * @方法名:testScheduling1 * @方法描述【方法功能描述】測試定時任務,沒三秒執(zhí)行一次 * @修改描述【修改描述】 * @版本:1.0 * @創(chuàng)建人:cc * @創(chuàng)建時間:2018年9月29日 上午10:26:20 * @修改人:cc * @修改時間:2018年9月29日 上午10:26:20 */ @Scheduled(fixedRate = 3000) public void testScheduling1() { System.out.println("執(zhí)行時間為"+new Date()+"執(zhí)行testScheduling1"); } } @Scheduled注解和之前spring使用xml配置定時任務類似: @Scheduled(fixedRate = 5000) :上一次開始執(zhí)行時間點之后5秒再執(zhí)行 @Scheduled(fixedDelay = 5000) :上一次執(zhí)行完畢時間點之后5秒再執(zhí)行 @Scheduled(initialDelay=1000, fixedRate=5000) :第一次延遲1秒后執(zhí)行,之后按fixedRate的規(guī)則每5秒執(zhí)行一次 @Scheduled(cron="*/5 * * * * *") :通過cron表達式定義規(guī)則
c.上述方法寫好后啟動服務看下控制臺結果:
2.異步調用
a.首先在spring boot主類上使用注解@EnableAsync啟動異步調用
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; //啟動異步調用 @EnableAsync @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
b.sping boot異步調用很簡單,只需使用@Async注解標明方法(接口方法)異步
package com.example.demo.test1.component; public interface TaskComponent { void test1() throws Exception; void test2() throws Exception; void test3() throws Exception; } package com.example.demo.test1.component.impl; import java.util.Random; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import com.example.demo.test1.component.TaskComponent; @Component public class TaskComponentImpl implements TaskComponent { public static Random random = new Random(); @Override @Async public void test1() throws InterruptedException { System.out.println("開始做任務一"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任務一,耗時:" + (end - start) + "毫秒"); } @Override @Async public void test2() throws InterruptedException { System.out.println("開始做任務二"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任務二,耗時:" + (end - start) + "毫秒"); } @Override @Async public void test3() throws InterruptedException { System.out.println("開始做任務三"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任務三,耗時:" + (end - start) + "毫秒"); } }
c.使用測試類進行測試:
package com.example.demo; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.web.server.LocalServerPort; import org.springframework.test.context.junit4.SpringRunner; import com.example.demo.test1.component.TaskComponent; @RunWith(SpringRunner.class) // 引入SpringBootTest并生成隨機接口 @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class AsyncTest { // 注入隨機接口 @LocalServerPort private int port; @Autowired private TaskComponent taskComponent; @Test public void testTask() { try { taskComponent.test1(); taskComponent.test2(); taskComponent.test3(); System.out.println("執(zhí)行主線程"); // 主線程休眠10秒等待上述異步方法執(zhí)行 Thread.sleep(10000); } catch (Exception e) { System.out.println(e); } } }
執(zhí)行結果如下;可以看出三個異步方法互不影響,且不影響主線程的運行
執(zhí)行主線程
開始做任務一
開始做任務二
開始做任務三
完成任務一,耗時:1401毫秒
完成任務二,耗時:4284毫秒
完成任務三,耗時:5068毫秒
d.對于這些異步執(zhí)行的調用往往會給我們帶來思考是不是異步調用越多越好,答案當然是否;所以在這里引入線程池來進行異步調用控制:
在spring boot主類上標注線程池:
package com.example.demo; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; //啟動定時任務 @EnableScheduling @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } // 啟動異步調用 @EnableAsync @Configuration class TaskPoolConfig { // 核心線程數(setCorePoolSize)10:線程池創(chuàng)建時候初始化的線程數 // 最大線程數(setMaxPoolSize)20:線程池最大的線程數,只有在緩沖隊列滿了之后才會申請超過核心線程數的線程 // 緩沖隊列(setQueueCapacity)200:用來緩沖執(zhí)行任務的隊列 // 允許線程的空閑時間(setKeepAliveSeconds)60秒:當超過了核心線程出之外的線程在空閑時間到達之后會被銷毀 // 線程池名的前綴(setThreadNamePrefix):設置好了之后可以方便我們定位處理任務所在的線程池 // 線程池對拒絕任務的處理策略(setRejectedExecutionHandler):這里采用了CallerRunsPolicy策略,當線程池沒有處理能力的時候,該策略會直接在 execute // 方法的調用線程中運行被拒絕的任務(setWaitForTasksToCompleteOnShutdown);如果執(zhí)行程序已關閉,則會丟棄該任務 // setWaitForTasksToCompleteOnShutdown(true)該方法就是這里的關鍵,用來設置線程池關閉的時候等待所有任務都完成再繼續(xù)銷毀其他的Bean,這樣這些異步任務的銷毀就會先于redis線程池的銷毀。 // 同時,這里還設置了setAwaitTerminationSeconds(60),該方法用來設置線程池中任務的等待時間,如果超過這個時候還沒有銷毀就強制銷毀,以確保應用最后能夠被關閉,而不是阻塞住。 @Bean("taskExecutor") public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(20); executor.setQueueCapacity(200); executor.setKeepAliveSeconds(60); executor.setThreadNamePrefix("taskExecutor-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setAwaitTerminationSeconds(60); return executor; } } }
在方法實現(xiàn)類上使用@Async的同時標注線程池:
package com.example.demo.test1.component.impl; import java.util.Random; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import com.example.demo.test1.component.TaskComponent; @Component public class TaskComponentImpl implements TaskComponent { public static Random random = new Random(); @Override @Async("taskExecutor") public void test1() throws InterruptedException { System.out.println("開始做任務一"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任務一,耗時:" + (end - start) + "毫秒"); } @Override @Async("taskExecutor") public void test2() throws InterruptedException { System.out.println("開始做任務二"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任務二,耗時:" + (end - start) + "毫秒"); } @Override @Async("taskExecutor") public void test3() throws InterruptedException { System.out.println("開始做任務三"); long start = System.currentTimeMillis(); Thread.sleep(random.nextInt(10000)); long end = System.currentTimeMillis(); System.out.println("完成任務三,耗時:" + (end - start) + "毫秒"); } }
再次調用測試來發(fā)現(xiàn)結果沒什么區(qū)別:
執(zhí)行主線程
開始做任務一
開始做任務二
開始做任務三
完成任務一,耗時:1117毫秒
完成任務二,耗時:3964毫秒
完成任務三,耗時:8886毫秒
接著我們修改線程池線程數為2:
executor.setCorePoolSize(2); executor.setMaxPoolSize(2);
再次啟動測試類可以看到,同時執(zhí)行的線程數為2,只有等待前一個線程結束才能執(zhí)行一個新的線程;
執(zhí)行主線程
開始做任務一
開始做任務二
完成任務二,耗時:620毫秒
開始做任務三
完成任務一,耗時:2930毫秒
完成任務三,耗時:4506毫秒
3.demo地址:鏈接地址
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。