前言
spring boot中调用异步并没有想象中的那么复杂,之前在实习过程中已经遇到了几次异步调用的需求,自己都没能顺利弄下来,这里正好来一个总结
创建定时任务
有时候系统中需要定时做一些任务,实现定时任务也没有想象中的复杂(毕竟只是实现hello world)
1、创建定时任务实现类
package com.learn.springbootScheduled.util;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class ScheduledTasks {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:SS");
@Scheduled(fixedRate=5000)
public void reportCurrentTime() {
System.out.println("现在时间:"+dateFormat.format(new Date(System.currentTimeMillis())));
}
}
标记Component注解,将类交给spring管理。在方法上打上@Scheduled标签,该注解常用有如下几个属性。
注解示例 | 作用 |
---|---|
@Scheduled(fixedRate=5000) | 上一次开始执行时间之后5秒再执行 |
@Scheduled(fixedDelay=5000) | 上一次执行完毕时间点之后5秒再执行 |
@Scheduled(initialDelay=1000,fixedRate=5000) | 第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次 |
@Scheduled(cron="*/5*****") | 通过cron表达式定义规则 |
2、在主加载类上添加@EnableScheduling注解
@SpringBootApplication
@EnableScheduling
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
测试结果
@Asyn实现异步
spring boot中实现异步操作也是非常容易的,比起之前什么的master-work模式,什么实现runnable接口,这个,已经非常简洁了,废话不多说,直接上示例,毕竟hello world级别的示例异常简单。
ps:关于同步和异步的区别,这里我就不解释了。
spring boot中我们只需要在方法上打上@Asyn标签就能实现异步操作。
异步调用(非回调)
1、编写异步逻辑实现类
package com.learn.springbootAsync.utils;
import java.util.Random;
import java.util.concurrent.Future;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
/**
*
* @author liman
* @createtime 2018年8月27日
* @contract 15528212893
* @comment: 异步功能函数
*/
@Component
public class Task {
public static Random random = new Random();
@Async
public void doTaskOne() throws InterruptedException {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
}
@Async
public void doTaskTwo() throws InterruptedException {
System.out.println("开始做任务二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
}
@Async
public void doTaskThree() throws InterruptedException {
System.out.println("开始做任务三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
}
}
2、主加载类中加上@EnableAsync注解
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3、测试代码
@Test
public void test() throws Exception {
task.doTaskOne();
task.doTaskTwo();
task.doTaskThree();
//主线程暂停
Thread.sleep(1000);
}
测试结果:
每次执行,任务完成先后顺序会不同。
@Async注解不能修饰静态方法,否则不会生效。
异步回调
所谓的回调就是主线程需要获取异步线程的返回结果,这个详细内容后面需要参考Java 高并发的相关书籍(之前学习的,都他妈忘光了)。
1、回调线程类
package com.learn.springbootAsync.utils;
import java.util.Random;
import java.util.concurrent.Future;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
/**
*
* @author liman
* @createtime 2018年8月27日
* @contract 15528212893
* @comment: 异步功能函数
*/
@Component
public class TaskFuture {
public static Random random = new Random();
@Async
public Future<String> doTaskOne() throws InterruptedException {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
return new AsyncResult<String>("任务一完成");
}
@Async
public Future<String> doTaskTwo() throws InterruptedException {
System.out.println("开始做任务二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
return new AsyncResult<String>("任务二完成");
}
@Async
public Future<String> doTaskThree() throws InterruptedException {
System.out.println("开始做任务三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
return new AsyncResult<String>("任务三完成");
}
}
2、主加载类中同样需要@EnableAsync注解
3、测试代码
@Test
public void testFuture() throws Exception {
long start = System.currentTimeMillis();
Future<String> task1 = taskFuture.doTaskOne();
Future<String> task2 = taskFuture.doTaskTwo();
Future<String> task3 = taskFuture.doTaskThree();
while(true) {
if(task1.isDone() && task2.isDone() && task3.isDone()) {
// 三个任务都调用完成,退出循环等待
System.out.println("回调线程已经全部执行结束");
break;
}
System.out.println("主线程正在进行其他操作");
Thread.sleep(1000);
}
long end = System.currentTimeMillis();
System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");
}
测试结果:
其中的完成任务X语句就是线程回调的结果 ,耗时统计就是主线程完成的工作,根据这个模板,主线程在完成调用其他线程的时候,还可以完成自己的一些逻辑。
使用自定义的线程池
spring boot中使用自定义的线程池,关于线程池的介绍,建议参考《Java 并发编程实战》一书
1、首先在spring boot主类中定义一个线程池
package com.learn.springbootThreadPoolSelf;
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.concurrent.ThreadPoolTaskExecutor;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@EnableAsync
@Configuration
class TaskPoolConfiguration{
@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.setAwaitTerminationSeconds(60);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
}
这里一大堆相关线程池属性的设置,还是有必要了解的,在前面推荐的书籍中都有介绍,后面的博客会总结到这一步,这里暂时忽略相关属性。
2、@Async中指定线程池名称
package com.learn.springbootThreadPoolSelf.util;
import java.util.Random;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
*
* @author liman
* @createtime 2018年8月27日
* @contract 15528212893
* @comment:
*
*/
@Component
public class TaskThreadPool {
public static Random random = new Random();
@Async("taskExecutor")
public void doTaskOne() throws Exception {
System.out.println("开始做任务一");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+" 完成任务一,耗时:" + (end - start) + "毫秒");
}
@Async("taskExecutor")
public void doTaskTwo() throws Exception {
System.out.println("开始做任务二");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+" 完成任务二,耗时:" + (end - start) + "毫秒");
}
@Async("taskExecutor")
public void doTaskThree() throws Exception {
System.out.println("开始做任务三");
long start = System.currentTimeMillis();
Thread.sleep(random.nextInt(1000));
long end = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+" 完成任务三,耗时:" + (end - start) + "毫秒");
}
}
@Async中指定了之前设置的线程池名称。这些线程逻辑代码会自动加入到线程池中。
3、测试代码
@Test
public void test() throws Exception {
task.doTaskOne();
task.doTaskTwo();
task.doTaskThree();
Thread.currentThread().join();
Thread.sleep(2000);
}
运行结果:
线程名称为设置的线程名,说明任务已经放到指定的线程池中进行执行。
总结
总体来说,到目前为止,5篇spring boot系列的博客,都只是存在helloworld级别,对实际开发有一定参考,每篇博客都有些不足,对于一些稍微麻烦的示例,都没有进行深入实现。这五篇博客也是每天在工作之余完成的,没有更多的时间进行修饰。后续依旧会总结spring boot的其他功能。目前这5篇基本总结了spring boot中比较常用的功能。