现在需要上传一个Excel表格,数据量几万条,而且,上传解析后还需要进行计算,然后插入数据库。
上传和解析,都很简单,但是,这里如果使用同步方式,那么:上传–>解析–>运算–>插入数据库;这个过程,前台的页面都是等待状态的,用户会以为页面卡死了。所以,这里需要做异步处理:
1.上传–>返回正在解析的标志;
2.解析–>运算–>插入数据库;
此时,当用户上传完文件后,页面立马跳转,解析,运算等工作,继续在后台进行,而用户可以不用等待。
Tip:
推荐使用 【方式二】:Spring Boot 基于注解方式开启异步任务处理,可以省去编写多线程的繁琐任务,并且只需要@EnableAsync 和 @Async 两个注解即可解决问题,so 完美
controller
/**
* 同步处理
* @return
*/
@RequestMapping(value = "test2",method = RequestMethod.GET)
public String test2(){
loginService.getTest2();
logger.info(Thread.currentThread().getName()+"==========主线程名");
return "同步,正在解析......";
}
serviceImpl
/**同步方法*/
@Override
public void getTest2(){
Building building = new Building();
synchronized (building){
try {
for (int i = 1;i <= 100;i++){
logger.info(Thread.currentThread().getName()+"----------同步:>"+i);
building.wait(200);
}
}catch (Exception ex){
ex.printStackTrace();
}
}
}
这种同步的方式处理,会发现,当这100次循环完成后,页面才会返回 :同步,正在解析…。当后台在循环处理时,前台的页面始终处于等待状态。可以发现,使用都是一个线程在处理:
使用线程池,创建新的线程去处理,如下:
controller
/**
* 异步处理1:线程池,创建新线程处理
* @return
*/
@RequestMapping(value = "test3",method = RequestMethod.GET)
public String test3(){
ExecutorService service = Executors.newFixedThreadPool(5);
RunnableTask1 task1 = new RunnableTask1();
service.execute(task1);
logger.info("=========》当前线程名:"+Thread.currentThread().getName());
return "异步,正在解析......";
}
线程任务
public class RunnableTask1 implements Runnable{
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void run(){
Building building = new Building();
synchronized (building){
try {
for (int i = 1;i <= 100;i++){
System.out.println(Thread.currentThread().getName()+"----------异步:>"+i);
building.wait(200);
}
}catch (Exception ex){
ex.printStackTrace();
}
}
}
}
我们看控制台会发现:主线程和处理任务的线程,不是一个线程;
即:当页面请求后,主线程会返回我们想要返回的标识,这里返回的是一个字符串:异步,正在解析…,而线程池新开了一个线程,在后台处理业务逻辑。所以,此时访问接口后,会立马返回,页面不用等待,处理逻辑在后台默默进行。控制台如下:
这种方式,是Spring Boot自身的一种异步方式,使用注解实现,非常方便。
我们在想要异步执行的方法上加上@Async
注解,在项目启动类上加上@EnableAsync
注解即可。注意:这里的异步方法,只能在自身之外调用,在本类调用是无效的。
项目启动类 SpringbootApplication
@EnableAsync //开启异步注解功能
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication .class, args);
}
}
controller
@RestController
@RequestMapping("tmall")
public class LoginController {
private final org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private LoginService loginService;
/**
* 异步处理2:使用springBoot自带async注解
*/
@RequestMapping(value = "test1",method = RequestMethod.GET)
public String test1(){
loginService.getTest1();
logger.info("============>"+Thread.currentThread().getName());
return "异步,正在解析......";
}
}
serviceImpl
/**异步方法
* 有@Async注解的方法,默认就是异步执行的,会在默认的线程池中执行,但是此方法不能在本类调用;启动类需添加直接开启异步执行@EnableAsync。
* */
@Async
@Override
public String getTest1(){
Building building = new Building();
synchronized (building){
try {
for (int i = 1;i <= 100;i++){
logger.info(Thread.currentThread().getName()+"----------异步:>"+i);
building.wait(200);
}
return "执行异步任务完毕";
}catch (Exception ex){
ex.printStackTrace();
}
}
return Thread.currentThread().getName()+"执行完毕";
}
看控制台,会发现,页面发出请求后,主线程会返回,而内置的线程池会新开线程,在后台执行任务。此时页面不用等待,可以继续其他操作。
可以看到,很多情况下,异步处理,是一种很常见,而且很高效的方式。相比较需要使用多线程解决异步,我们肯定更喜欢使用 Spring Boo t自带的注解方式,只用两个注解即可了
。
本文为转载文章,在原文基础上有所修改,仅用作学习记录
如果本文对你有所帮助,那就给我点个赞呗
End