SpringBoot學習四:定時任務和異步調用

相關文章

SpringBoot 學習一java

SpringBoot 學習二:操做數據庫spring

SpringBoot學習三:異常處理和記錄日誌數據庫

前言

今天來學習一下經過 SpringBoot 來實現一個定時任務和異步調用。springboot

定時任務

在 Spring 中 能夠經過 @EnableScheduling @Scheduled 這兩個註解來實現咱們的定時任務,接下來看下這兩個註解:dom

@EnableScheduling

在配置類上標註了 @EnableScheduling 註解後,即表示 Spring 開啓了定時任務,在 Spring 容器中標註了  @Scheduled 註解的方法都會被檢測到,會進行執行;若是須要更多更加細粒度的控制,配置類能夠實現 SchedulingConfigurer 類來進行實現,以下所示:異步

@Configuration
@EnableScheduling
public class MyScheduleTaskConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
//        taskRegistrar.setCronTasks();
//        taskRegistrar.setFixedDelayTasks();
//        taskRegistrar.setTriggerTasksList();
    }

    @Bean(destroyMethod="shutdown")
    public Executor taskExecutor() {
         return Executors.newScheduledThreadPool(100);
     }
}

 @Scheduled

在 Spring 中開啓了定時任務後,就可使用  @Scheduled  這個註解來定義須要定時執行的方法了,接下來看下它的一個定義:ide

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
    // cron 表達式
	String cron() default "";
    // 時區
	String zone() default "";
    // 時間間隔 單位毫秒
	long fixedDelay() default -1;
    // 時間間隔,單位毫秒,使用 String 表示
	String fixedDelayString() default "";
    // 時間間隔 單位毫秒
	long fixedRate() default -1;
    // 時間間隔,單位毫秒,使用 String 表示
	String fixedRateString() default "";
    // 初始化時間,即任務在多少秒後開始執行第一次
	long initialDelay() default -1;
    // 初始化時間,即任務在多少秒後開始執行第一次,使用 String 表示
	String initialDelayString() default "";

}


接下來,就來測試一下 cron ,fixedDelay 和 fixedRate 這三個屬性:學習

cron 

cron 屬性,即便用 cron 表達式來控制任務的定時執行,以下所示:測試

/**
 * 定時任務
 */
@Component
public class ScheduledTask {

    private static final DateFormat dateFormat = new SimpleDateFormat("HH:m:ss");

    @Scheduled(cron = "0/5 * * * * ?")
    public void printTime3() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
    }
}

啓動程序,查看控制檯打印,會 5 秒打印一次:spa

time: 14:19:00
time: 14:19:05
time: 14:19:10
time: 14:19:15

若是,任務在 5 秒以內已經執行完畢,會怎麼樣呢?

@Scheduled(cron = "0/5 * * * * ?")
    public void printTime3() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
        Thread.sleep(2000);
    }

time: 14:20:50
time: 14:20:55
time: 14:21:00
time: 14:21:05

由打印的結果看到,若是任務在間隔時間以內已經執行完畢,則下一個任務也會等到時間間隔結束後纔會執行下一次任務。

 

若是,任務在 5 秒以內尚未執行完,則會怎麼樣呢?

@Scheduled(cron = "0/5 * * * * ?")
    public void printTime3() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
        Thread.sleep(8000);
    }

time: 14:28:25
time: 14:28:35
time: 14:28:45
time: 14:28:55

由打印結果能夠看到,若是任務在時間間隔以內沒有執行完畢,則下一個任務會留到下一個週期纔會執行,由於 Spring 在處理使用cron表達式這種定時任務時,它會在任務開始時判斷任務是否能夠執行,若是能夠則執行,若是不能夠,那麼它將不執行這次任務,等待下一次執行。

fixedDelay

fixedDelay 屬性表示的是從上一個任務結束到下一個任務開始之間的時間間隔,若是上一個任務執行超時了,下一個任務會等待上一個任務執行完畢後才執行:

@Scheduled(fixedDelay = 5000)
    public void printTime2() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
    }

time: 14:38:29
time: 14:38:34
time: 14:38:39
time: 14:38:44
time: 14:38:49

若是,任務在時間間隔內執行完畢:

@Scheduled(fixedDelay = 5000)
    public void printTime2() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
        Thread.sleep(2000);
    }

每隔 7 秒打印一次

time: 14:40:24
time: 14:40:31
time: 14:40:38
time: 14:40:45
time: 14:40:52

從打印結果能夠看到,若是任務在時間間隔以內已經執行完畢,下一個任務等待的時間將會是任務執行的時間+時間間隔;

若是,任務在之間間隔以內沒有執行完畢:

@Scheduled(fixedDelay = 5000)
    public void printTime2() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
        Thread.sleep(8000);
    }

每隔 13 秒打印一次

time: 14:44:20
time: 14:44:33
time: 14:44:46
time: 14:44:59
time: 14:45:12

從打印結果能夠看到,若是任務在時間間隔以內尚未執行完畢,下一個任務等待的時間將會是任務執行的時間+時間間隔;

從上面兩個例子能夠看到,fixedDelay  主要關注的是上一個任務結束的時間到下一個任務開始的時間之間的時間間隔,也就是下一個任務開始執行前須要等待的時間=上一個任務執行時間+時間間隔

fixedRate

fixedRate 表示的是兩個任務開始執行的時間間隔,

@Scheduled(fixedRate = 5000)
    public void printTime() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
    }

time: 14:53:38
time: 14:53:43
time: 14:53:48
time: 14:53:53
time: 14:53:58

和上面的 cron 和 fixedDelay 同樣,都是 5 秒執行一次。

若是,任務在時間間隔以內已經執行完畢,以下:

@Scheduled(fixedRate = 5000)
    public void printTime() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
        Thread.sleep(2000);
    }

time: 14:55:04
time: 14:55:09
time: 14:55:14
time: 14:55:19
time: 14:55:24

能夠看到,這裏就和 fixedDelay 不同了,fixedDelay 下一個任務等待的時間等於上一個任務執行時間+時間間隔,而這裏,從打印的結果能夠看到,若是上一個任務在時間間隔以內已經執行完畢,則下一個任務仍是會等待到時間間隔結束後纔會執行。說明,fixedRate 表示的時間間隔是兩個任務開始的時間間隔。

若是,任務在時間間隔以內沒有執行完畢;

@Scheduled(fixedRate = 5000)
    public void printTime() throws InterruptedException {
        System.out.println("time: " + dateFormat.format(new Date()));
        Thread.sleep(8000);
    }

time: 15:0:24
time: 15:0:32
time: 15:0:40
time: 15:0:48
time: 15:0:56

從打印結果能夠看到,若是任務在時間間隔以內尚未執行完畢,則下一個任務會等待上一個任務執行完畢後纔開始執行,此時時間間隔再也不是 5秒而是8秒。

結論

一、fixedRate 強調的是上一次任務的開始時間 到 下一次任務的開始時間的間隔
二、fixedDelay 調的是上一次任務的結束時間 到 下一次任務的開始時間的間隔
三、cron表達式配置了在哪一刻執行任務,會在任務開始時判斷任務是否能夠執行,若是能則執行,不能則會跳過本次執行

異步調用

在 Spring Boot 中經過 @EnableAsync@Async 這兩個註解來實現異步調用的,接下來看下這兩個註解:

@EnableAsync

@EnableAsync 該註解主要是用來開啓Spring 異步調用的,用在配置類上,

@SpringBootApplication
@EnableAsync
public class MyspringbootApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyspringbootApplication.class, args);
    }
}

@Async

當 Spring 開啓了 異步調用後,就可使用 @Async 定義須要異步執行的方法了,

/**
 * 異步調用任務
 */
@Component
public class AsyncTask {

    private Random random = new Random();

    @Async
    public void task1() throws InterruptedException {
        System.out.println("任務一開始...");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        System.out.println("任務一耗時:" + (System.currentTimeMillis() - start));
        System.out.println("任務一完成...");
    }

    @Async
    public void task2() throws InterruptedException {
        System.out.println("任務二開始...");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        System.out.println("任務二耗時:" + (System.currentTimeMillis() - start));
        System.out.println("任務二完成...");
    }

    @Async
    public void task3() throws InterruptedException {
        System.out.println("任務三開始...");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        System.out.println("任務三耗時:" + (System.currentTimeMillis() - start));
        System.out.println("任務三完成...");
    }
}

測試:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MyspringbootApplication.class)
public class MyTest {

    @Autowired
    private AsyncTask task;

    @Test
    public void test() throws InterruptedException {
        task.task1();
        task.task2();
        task.task3();
        Thread.sleep(60 * 1 * 1000);
    }
}

任務一開始...
任務三開始...
任務二開始...
任務三耗時:2018
任務三完成...
任務一耗時:4027
任務一完成...
任務二耗時:9364
任務二完成...

 

若是不想使用默認的線程池,咱們還能夠自定義線程池,只須要實現 AsyncConfigurer 便可:

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Bean(name = "taskExecutor")
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(7);
        executor.setMaxPoolSize(42);
        executor.setQueueCapacity(11);
        executor.setThreadNamePrefix("MyExecutor-");
        executor.initialize();
        // 關閉資源
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return null;
    }
}

使用自定義線程池:

@Async("taskExecutor")
    public void task1() throws InterruptedException {
        System.out.println("任務一開始...");
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        System.out.println("任務一耗時:" + (System.currentTimeMillis() - start));
        System.out.println("任務一完成...");
    }

 

在線程執行的時候,能夠有返回值 Future,咱們能夠返回 AsyncResult:

@Async("taskExecutor")
    public Future<String> test4(){
        System.out.println("執行任務,返回結果");
        return new AsyncResult<>("任務執行結果:5");
    }

測試:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MyspringbootApplication.class)
public class MyTest {

    @Autowired
    private AsyncTask task;

    @Test
    public void test2() throws InterruptedException, ExecutionException, TimeoutException {
        Future<String> future = task.test4();
        System.out.println("任務是否完成:" + future.isDone());
        String result = future.get(5, TimeUnit.SECONDS);
        System.out.println(result);
        System.out.println("任務是否完成:" + future.isDone());
    }
}

結果:

任務是否完成:false
執行任務,返回結果
任務執行結果:5
任務是否完成:true

以上就是經過 SpringBoot 來操做定時任務和異步調用。

相關文章
相關標籤/搜索