SpringBoot實現(xiàn)定時任務(wù)和異步調(diào)用
本文實例為大家分享了SpringBoot實現(xiàn)定時任務(wù)和異步調(diào)用的具體代碼,供大家參考,具體內(nèi)容如下
環(huán)境:
jdk1.8;spring boot2.0.2;Maven3.3
摘要說明:
定時任務(wù):定時任務(wù)是業(yè)務(wù)場景中經(jīng)常出現(xiàn)的一種情況如:定時發(fā)送郵件,短信、定時統(tǒng)計監(jiān)控數(shù)據(jù)、定時對賬等
異步調(diào)用:一個都買流程可能包括下單、發(fā)貨通知、短信推送、消息推送等,其實除了下單這個主要程序是主程序,其他子程序可以同時進行且不影響主程序的運行,這個時候就可以使用異步調(diào)用來調(diào)用這些子程序;
步驟:
1.定時任務(wù)
a.在spring boot主類上使用注解@EnableScheduling啟動定時任務(wù):
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
//啟動定時任務(wù)
@EnableScheduling
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
b.實現(xiàn)定時任務(wù)(使用@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
* @類描述:【類描述】用于測試定時任務(wù) @版本:1.0
* @創(chuàng)建人:cc
* @創(chuàng)建時間:2018年9月29日上午10:19:37
*/
@Component
public class SchedulingComponent {
/**
*
* @方法名:testScheduling1
* @方法描述【方法功能描述】測試定時任務(wù),沒三秒執(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配置定時任務(wù)類似:
@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.上述方法寫好后啟動服務(wù)看下控制臺結(jié)果:

2.異步調(diào)用
a.首先在spring boot主類上使用注解@EnableAsync啟動異步調(diào)用
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
//啟動異步調(diào)用
@EnableAsync
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
b.sping boot異步調(diào)用很簡單,只需使用@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("開始做任務(wù)一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任務(wù)一,耗時:" + (end - start) + "毫秒");
}
@Override
@Async
public void test2() throws InterruptedException {
System.out.println("開始做任務(wù)二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任務(wù)二,耗時:" + (end - start) + "毫秒");
}
@Override
@Async
public void test3() throws InterruptedException {
System.out.println("開始做任務(wù)三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任務(wù)三,耗時:" + (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í)行結(jié)果如下;可以看出三個異步方法互不影響,且不影響主線程的運行
執(zhí)行主線程
開始做任務(wù)一
開始做任務(wù)二
開始做任務(wù)三
完成任務(wù)一,耗時:1401毫秒
完成任務(wù)二,耗時:4284毫秒
完成任務(wù)三,耗時:5068毫秒
d.對于這些異步執(zhí)行的調(diào)用往往會給我們帶來思考是不是異步調(diào)用越多越好,答案當然是否;所以在這里引入線程池來進行異步調(diào)用控制:
在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;
//啟動定時任務(wù)
@EnableScheduling
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
// 啟動異步調(diào)用
@EnableAsync
@Configuration
class TaskPoolConfig {
// 核心線程數(shù)(setCorePoolSize)10:線程池創(chuàng)建時候初始化的線程數(shù)
// 最大線程數(shù)(setMaxPoolSize)20:線程池最大的線程數(shù),只有在緩沖隊列滿了之后才會申請超過核心線程數(shù)的線程
// 緩沖隊列(setQueueCapacity)200:用來緩沖執(zhí)行任務(wù)的隊列
// 允許線程的空閑時間(setKeepAliveSeconds)60秒:當超過了核心線程出之外的線程在空閑時間到達之后會被銷毀
// 線程池名的前綴(setThreadNamePrefix):設(shè)置好了之后可以方便我們定位處理任務(wù)所在的線程池
// 線程池對拒絕任務(wù)的處理策略(setRejectedExecutionHandler):這里采用了CallerRunsPolicy策略,當線程池沒有處理能力的時候,該策略會直接在 execute
// 方法的調(diào)用線程中運行被拒絕的任務(wù)(setWaitForTasksToCompleteOnShutdown);如果執(zhí)行程序已關(guān)閉,則會丟棄該任務(wù)
// setWaitForTasksToCompleteOnShutdown(true)該方法就是這里的關(guān)鍵,用來設(shè)置線程池關(guān)閉的時候等待所有任務(wù)都完成再繼續(xù)銷毀其他的Bean,這樣這些異步任務(wù)的銷毀就會先于Redis線程池的銷毀。
// 同時,這里還設(shè)置了setAwaitTerminationSeconds(60),該方法用來設(shè)置線程池中任務(wù)的等待時間,如果超過這個時候還沒有銷毀就強制銷毀,以確保應(yīng)用最后能夠被關(guān)閉,而不是阻塞住。
@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("開始做任務(wù)一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任務(wù)一,耗時:" + (end - start) + "毫秒");
}
@Override
@Async("taskExecutor")
public void test2() throws InterruptedException {
System.out.println("開始做任務(wù)二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任務(wù)二,耗時:" + (end - start) + "毫秒");
}
@Override
@Async("taskExecutor")
public void test3() throws InterruptedException {
System.out.println("開始做任務(wù)三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(10000));
long end = System.currentTimeMillis();
System.out.println("完成任務(wù)三,耗時:" + (end - start) + "毫秒");
}
}
再次調(diào)用測試來發(fā)現(xiàn)結(jié)果沒什么區(qū)別:
執(zhí)行主線程
開始做任務(wù)一
開始做任務(wù)二
開始做任務(wù)三
完成任務(wù)一,耗時:1117毫秒
完成任務(wù)二,耗時:3964毫秒
完成任務(wù)三,耗時:8886毫秒
接著我們修改線程池線程數(shù)為2:
executor.setCorePoolSize(2); executor.setMaxPoolSize(2);
再次啟動測試類可以看到,同時執(zhí)行的線程數(shù)為2,只有等待前一個線程結(jié)束才能執(zhí)行一個新的線程;
執(zhí)行主線程
開始做任務(wù)一
開始做任務(wù)二
完成任務(wù)二,耗時:620毫秒
開始做任務(wù)三
完成任務(wù)一,耗時:2930毫秒
完成任務(wù)三,耗時:4506毫秒
3.demo地址:鏈接地址
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
基于Properties類操作.properties配置文件方法總結(jié)
這篇文章主要介紹了Properties類操作.properties配置文件方法總結(jié),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
6種常見的SpringBoot攔截器使用場景及實現(xiàn)方式
這篇文章主要為大家詳細介紹了SpringBoot中6種常見的攔截器使用場景及其實現(xiàn)方式,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2025-04-04
SpringBoot+MyBatisPlus對Map中Date格式轉(zhuǎn)換處理的方法詳解
在?SpringBoot?項目中,?如何統(tǒng)一?JSON?格式化中的日期格式。本文將為大家介紹一種方法:利用MyBatisPlus實現(xiàn)對Map中Date格式轉(zhuǎn)換處理,需要的可以參考一下2022-10-10
springboot基于Mybatis mysql實現(xiàn)讀寫分離
這篇文章主要介紹了springboot基于Mybatis mysql實現(xiàn)讀寫分離,需要的朋友可以參考下2019-06-06
Java報錯java.awt.AWTException: AWT的解決方法
在Java圖形用戶界面(GUI)編程中,java.awt.AWTException是一個常見的異常,它通常與AWT(Abstract Window Toolkit)組件相關(guān),這個異??赡茉趪L試進行與窗口、圖形環(huán)境或系統(tǒng)剪貼板等操作時拋出,本文將詳細探討AWTException的成因,并提供多種解決方案2024-12-12

