异步调用就是不用等待结果的返回就执行后面的逻辑,同步调用则需要等带结果再执行后面的逻辑。

通常我们使用异步操作都会去创建一个线程执行一段逻辑,然后把这个线程丢到线程池中去执行,代码如下:

ExecutorService executorService = Executors.newFixedThreadPool();
executorService.execute(() -> {
try {
// 业务逻辑
} catch (Exception e) {
e.printStackTrace();
} finally {
}
});

这样的方式看起来没那么优雅,尽管用了java的lambda。在Spring Boot中有一种更简单的方式来执行异步操作,只需要一个@Async注解即可。

@Async
public void saveLog() {
System.err.println(Thread.currentThread().getName());
}

我们可以直接在Controller中调用这个业务方法,它就是异步执行的,会在默认的线程池中去执行。需要注意的是一定要在外部的类中去调用这个方法,如果在本类调用是不起作用的,比如this.saveLog()。 最后在启动类上开启异步任务的执行,添加@EnableAsync即可。

另外关于执行异步任务的线程池我们也可以自定义,首先我们定义一个线程池的配置类,用来配置一些参数,具体代码如下:

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration; /**
* 异步任务线程池配置
*
* @author yinjihuan
*/
@Configuration
@ConfigurationProperties(prefix = "spring.task.pool")
public class TaskThreadPoolConfig { //核心线程数
private int corePoolSize = ; //最大线程数
private int maxPoolSize = ; //线程池维护线程所允许的空闲时间
private int keepAliveSeconds = ; //队列长度
private int queueCapacity = ; //线程名称前缀
private String threadNamePrefix = "FSH-AsyncTask-"; public String getThreadNamePrefix() {
return threadNamePrefix;
} public void setThreadNamePrefix(String threadNamePrefix) {
this.threadNamePrefix = threadNamePrefix;
} public int getCorePoolSize() {
return corePoolSize;
} public void setCorePoolSize(int corePoolSize) {
this.corePoolSize = corePoolSize;
} public int getMaxPoolSize() {
return maxPoolSize;
} public void setMaxPoolSize(int maxPoolSize) {
this.maxPoolSize = maxPoolSize;
} public int getKeepAliveSeconds() {
return keepAliveSeconds;
} public void setKeepAliveSeconds(int keepAliveSeconds) {
this.keepAliveSeconds = keepAliveSeconds;
} public int getQueueCapacity() {
return queueCapacity;
} public void setQueueCapacity(int queueCapacity) {
this.queueCapacity = queueCapacity;
} }

然后我们重新定义线程池的配置:

import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @Configuration
public class AsyncTaskExecutePool implements AsyncConfigurer {
private Logger logger = LoggerFactory.getLogger(AsyncTaskExecutePool.class); @Autowired
private TaskThreadPoolConfig config; @Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(config.getCorePoolSize());
executor.setMaxPoolSize(config.getMaxPoolSize());
executor.setQueueCapacity(config.getQueueCapacity());
executor.setKeepAliveSeconds(config.getKeepAliveSeconds());
executor.setThreadNamePrefix(config.getThreadNamePrefix());
//线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy
//AbortPolicy:直接抛出java.util.concurrent.RejectedExecutionException异常 -->
//CallerRunsPolicy:主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,可以有效降低向线程池内添加任务的速度 -->
//DiscardOldestPolicy:抛弃旧的任务、暂不支持;会导致被丢弃的任务无法再次被执行 -->
//DiscardPolicy:抛弃当前任务、暂不支持;会导致被丢弃的任务无法再次被执行 -->
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
} @Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {// 异步任务中异常处理
return new AsyncUncaughtExceptionHandler() {
@Override
public void handleUncaughtException(Throwable arg0, Method arg1, Object... arg2) {
logger.error("=========================="+arg0.getMessage()+"=======================", arg0);
logger.error("exception method:" + arg1.getName());
}
};
}
}

配置完之后我们的异步任务执行的线程池就是我们自定义的了,我们可以通过在属性文件里面配置线程池的大小等等信息,也可以使用默认的配置:

spring.task.pool.maxPoolSize=100  

最后讲下线程池配置的拒绝策略,当我们的线程数量高于线程池的处理速度时,任务会被缓存到本地的队列中,队列也是有大小的,如果超过了这个大小,我们需要有拒绝的策略,不然就会内存溢出了,目前支持2中拒绝策略:
- AbortPolicy: 直接抛出java.util.concurrent.RejectedExecutionException异常
- CallerRunsPolicy: 主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,可以有效降低向线程池内添加任务的速度

建议大家用CallerRunsPolicy策略,因为当队列中的任务满了之后,如果直接抛异常,那么这个任务就会被丢弃,如果是CallerRunsPolicy策略会用主线程去执行,就是同步执行,最起码这样任务不会丢弃。

Spring Boot Async异步执行的更多相关文章

  1. Spring Boot @Async 异步任务执行

    1.任务执行和调度 Spring用TaskExecutor和TaskScheduler接口提供了异步执行和调度任务的抽象. Spring的TaskExecutor和java.util.concurre ...

  2. Spring Boot 之异步执行方法

    前言: 最近的时候遇到一个需求,就是当服务器接到请求并不需要任务执行完成才返回结果,可以立即返回结果,让任务异步的去执行.开始考虑是直接启一个新的线程去执行任务或者把任务提交到一个线程池去执行,这两种 ...

  3. 使用spring的@Async异步执行方法

    应用场景: 1.某些耗时较长的而用户不需要等待该方法的处理结果 2.某些耗时较长的方法,后面的程序不需要用到这个方法的处理结果时 在spring的配置文件中加入对异步执行的支持 <beans x ...

  4. spring boot @Async异步注解上下文透传

    上一篇文章说到,之前使用了@Async注解,子线程无法获取到上下文信息,导致流量无法打到灰度,然后改成 线程池的方式,每次调用异步调用的时候都手动透传 上下文(硬编码)解决了问题. 后面查阅了资料,找 ...

  5. Spring boot 配置异步处理执行器

    示例如下: 1. 新建Maven 项目 async-executor 2.pom.xml <project xmlns="http://maven.apache.org/POM/4.0 ...

  6. Spring开启方法异步执行

    @EnableAsync @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(Async ...

  7. C#.NET使用Task,await,async,异步执行控件耗时事件(event),不阻塞UI线程和不跨线程执行UI更新,以及其他方式比较

    使用Task,await,async,异步执行事件(event),不阻塞UI线程和不跨线程执行UI更新 使用Task,await,async 的异步模式 去执行事件(event) 解决不阻塞UI线程和 ...

  8. Spring Boot 定时+多线程执行

    Spring Boot 定时任务有多种实现方式,我在一个微型项目中通过注解方式执行定时任务. 具体执行的任务,通过多线程方式执行,单线程执行需要1小时的任务,多线程下5分钟就完成了. 执行效率提升10 ...

  9. 利用Spring的@Async异步处理改善web应用中耗时操作的用户体验

    Web应用中,有时会遇到一些耗时很长的操作(比如:在后台生成100张报表再呈现,或 从ftp下载若干文件,综合处理后再返回给页面下载),用户在网页上点完按钮后,通常会遇到二个问题:页面超时.看不到处理 ...

随机推荐

  1. 达人篇:5)公差的正态分布与CPK与制程能力(重要)

    本章目的:明确公差分布(Tolerance Distribution)也有自己的形状,了解CPK概念. 1.正态分布(常态分布)normal distribution的概念 统计分析常基于这样的假设: ...

  2. Python模拟鼠标和键盘操作实现重复性操作

    前言 由于工作需要,要利用某软件去采集数据,做重复的动作大概500多次.所以想写一个程序代替人,去点击和输入. 一开始的思路有两个:1.用Python或者windows对此软件直接操作.2.利用Pyt ...

  3. 【OpenCV-Python】-图像阀值

    参考:Opencv官方教程 1.简单阀值 cv2.threshold , cv2.adaptiveThreshold当像素值高于阀值时,我们给这个像素赋予一个新值(可能是白色),否则我们给它赋予另外一 ...

  4. Vector bit-select and part-select addressing verilog片选写法

    大端 m m[ a +: b ] == m[ (a+b-1) : a ] m[ a -: b ] == m[ a : (a-b+1) ] 小端 n n[ a +: b ] == n[ a : (a+b ...

  5. MySQL初始化与用户配置

    数据库初始化 默认情况下,数据已经初始化好,数据可参见默认配置文件/etc/my.cnf 在其他位置重新初始化MySQL数据库: basedir是mysql的安装根目录,ldata是数据初始化的目录 ...

  6. MySQL约束和修改数据表知识集结

    一.约束 划分标准:功能.数据列的数目 功能: (1)NOT NULL(非空约束) (2)PRIMARY KEY(主键约束) (3)UNIQUE(唯一约束) (4)DEFAULT(默认约束) (5)F ...

  7. python+requests抓取页面图片

    前言: 学完requests库后,想到可以利用python+requests爬取页面图片,想到实战一下.依照现在所学只能爬取图片在html页面的而不能爬取由JavaScript生成的图片,所以我选取饿 ...

  8. MySQL查询近一个月的数据

    MySQL查询近一个月的数据 近一个月统计SQL select user_id, user_name, createtime from t_user where DATE_SUB(CURDATE(), ...

  9. ubuntu 64上的GCC如何编译32位程序

    运行命令 gcc -v 显示: Target: x86_64-linux-gnu 所以,我这里的gcc默认生成64位的程序. 如果想编出32位的程序,就要加 -m32选项.可是我尝试了,还是不行. 原 ...

  10. C 标准库 - string.h之memchr使用

    memchr Locate character in block of memory,Searches within the first num bytes of the block of memor ...