一,为什么要使用async异步线程池?

1,在生产环境中,有一些需要延时处理的业务场景:

例如:发送电子邮件,

给手机发短信验证码

大数据量的查询统计

远程抓取数据等

这些场景占用时间较长,而用户又没有必须立刻得到返回数据的需求,

我们如果让用户占用到服务器的连接长时间等待也没有必要,

这时异步处理是优先选择。

2,使用线程池的好处?

第一,提高资源利用率:可以重复利用已经创建了的线程

第二,提高响应速度:如果有线程处于等待分配任务状态时,则任务到来时无需创建线程就能被执行

第三,具有可管理性:线程池能减少创建和销毁线程带来的系统开销

说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

对应的源码可以访问这里获取: https://github.com/liuhongdi/

说明:作者:刘宏缔 邮箱: 371125307@qq.com

二,演示项目的相关信息

1,项目地址:

https://github.com/liuhongdi/asyncmail

2,项目的说明:

regmail:演示异步发送一封注册成功的邮件

sleep:同步执行sleep1秒

asyncsleep:演示异步执行十个各sleep1秒的线程

3,项目的结构,如图:

三,演示项目的配置文件说明:

1,pom.xml

        <!--mail begin-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!--mail end--> <!--thymeleaf begin-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--thymeleaf end-->

引入mail和thymeleaf两个依赖,其中thymeleaf是作为html格式邮件内容的模板

2,application.properties

spring.mail.host=smtp.163.com
spring.mail.username=demouser@163.com
spring.mail.password=demopassword
spring.mail.default-encoding=UTF-8
spring.mail.protocol=smtps
spring.mail.port=465
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true

说明:此处注意:如果是在阿里云ecs上运行,

不要使用25的smtp端口,要使用带ssl的smtps,端口是465

另外:这里配置的password,是邮箱的授权码,不是登录密码,

有疑问可以参见这一篇:

https://www.cnblogs.com/architectforest/p/12924395.html

四,java代码说明:

1,AsyncConfig.java

//线程池的配置
@Configuration
@EnableAsync
public class AsyncConfig { @Bean(name = "taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数,它是可以同时被执行的线程数量
executor.setCorePoolSize(5);
// 设置最大线程数,缓冲队列满了之后会申请超过核心线程数的线程
executor.setMaxPoolSize(10);
// 设置缓冲队列容量,
executor.setQueueCapacity(20);
// 设置线程生存时间(秒),当超过了核心线程出之外的线程在生存时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
// 设置线程名称前缀
executor.setThreadNamePrefix("threadpool-");
// 设置拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
//初始化
executor.initialize();
return executor;
}
}

说明:线程池的配置文件,

这个有个问题:核心线程数设置为多少为宜?

设N为cpu的核心数量,则

如果是CPU密集型应用,则线程池大小设置为N+1

如果是IO密集型应用,则线程池大小设置为2N+1

这个是常用的一个设置参考,具体是否适用自己的业务还要在生产环境中观察

但可以确认的是:线程数不是越多越好,因为所在机器上的cpu等硬件并没有变化,

异步也只是提高吞吐量,并不能加快任务的执行

2,MailUtil.java

@Component
public class MailUtil { @Resource
private JavaMailSender javaMailSender; @Resource
TemplateEngine templateEngine; //发送html内容的邮件,使用thymeleaf渲染页面
public void sendHtmlMail(String from, String[] to, String[] cc, String[] bcc, String subject, String templateName, HashMap<String,String> content) throws MessagingException {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setSubject(subject);
helper.setFrom(from);
helper.setTo(to);
//抄送,收到邮件用户可以看到其他收件人
if (cc != null && cc.length > 0) {
helper.setCc(cc);
}
//密送 收到邮件用户看不到其他收件人
if (bcc != null && bcc.length > 0) {
helper.setBcc(bcc);
}
helper.setSentDate(new Date());
//生成邮件模板上的内容
Context context = new Context();
if (content != null && content.size() > 0) {
for (String key : content.keySet()) {
context.setVariable(key, content.get(key));
}
}
String process = templateEngine.process(templateName, context);
helper.setText(process,true);
javaMailSender.send(mimeMessage);
}
}

说明:功能是发送html内容的邮件,所以用到了templateEngine

3,邮件内容:regmail.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>您好,注册成功! 以下是您在本网站的注册信息:</h1>
<table border="1">
<tr>
<td>用户名</td>
<td th:text="${username}">${username}</td>
</tr>
<tr>
<td>昵称</td>
<td th:text="${nickname}">${nickname}</td>
</tr>
<tr>
<td>ID</td>
<td th:text="${id}">${id}</td>
</tr>
</table>
<div style="color: #ff1a0e">本站网址:http://springio.com</div>
</body>
</html>

4,MailServiceImpl.java

@Service
public class MailServiceImpl implements MailService { @Resource
private MailUtil mailUtil; //异步发送html格式的邮件
@Async
@Override
public void sendHtmlMail() {
String from = "demouser@163.com";
String[] to = {"371125307@qq.com"};
String subject = "恭喜您成功注册老刘代码库网站";
HashMap<String,String> content= new HashMap<String,String>();
content.put("username","laoliu");
content.put("nickname","老刘");
content.put("id","0000001");
String templateName= "mail/regmail.html";
try {
mailUtil.sendHtmlMail(from, to, null, null, subject, templateName, content);
} catch (MessagingException e) {
e.printStackTrace();
System.out.println("邮件发送出错");
}
}
}

功能:生成邮件的内容,注意这里指定的模板文件和模板上的变量

5,SlowServiceImpl.java

@Service
public class SlowServiceImpl implements SlowService { private static Logger log= LoggerFactory.getLogger(SlowServiceImpl.class); //sleep 1秒
@Override
public void sleepawhile(){
long startTime = System.currentTimeMillis();
log.info("function sleep begin");
try {
Thread.sleep(1000); //延时1秒
}
catch(InterruptedException e) {
e.printStackTrace();
}
log.info("function sleep end");
} //sleep 1秒,异步执行,并返回一个统计用的字串
@Async
@Override
public Future<String> asyncsleepawhile(int i){
log.info("async function sleep begin");
String start=TimeUtil.getMilliTimeNow();
try {
Thread.sleep(1000); //延时1秒
}
catch(InterruptedException e) {
e.printStackTrace();
}
log.info("async function sleep end");
String end=TimeUtil.getMilliTimeNow();
return new AsyncResult<>(String.format("第{%s}个异步调用asyncsleepawhile方法:开始时间:%s,结束时间:%s", i,start,end));
}
}

仅供演示用,两个方法:一个同步sleep,一个异步sleep

6,HomeController.java

@RequestMapping("/home")
@Controller
public class HomeController { private static Logger log= LoggerFactory.getLogger(HomeController.class); @Resource
private MailService mailService; @Resource
private SlowService slowService; //异步发送一封注册成功的邮件
@GetMapping("/regmail")
@ResponseBody
public String regMail(ModelMap modelMap) {
mailService.sendHtmlMail();
return "mail sended";
} //同步sleep1秒
@GetMapping("/sleep")
@ResponseBody
public String sleep() {
System.out.println(TimeUtil.getMilliTimeNow()+" controller begin");
slowService.sleepawhile();
System.out.println(TimeUtil.getMilliTimeNow()+" controller end");
return "mail sended";
} //异步执行sleep1秒10次
@GetMapping("/asyncsleep")
@ResponseBody
public Map<String, Object> asyncsleep() throws ExecutionException, InterruptedException { long start = System.currentTimeMillis();
Map<String, Object> map = new HashMap<>();
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Future<String> future = slowService.asyncsleepawhile(i);
futures.add(future);
}
List<String> response = new ArrayList<>();
for (Future future : futures) {
String string = (String) future.get();
response.add(string);
}
map.put("data", response);
map.put("消耗时间", String.format("任务执行成功,耗时{%s}毫秒", System.currentTimeMillis() - start));
return map;
}
}

五,效果测试:

1,发送注册邮件:

访问:

http://127.0.0.1:8080/home/regmail

收到邮件:

2,测试异步线程池:

访问:

http://127.0.0.1:8080/home/asyncsleep

查看输出:

{"data":["第{0}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:10.937,结束时间:2020-07-27 17:10:11.941",
"第{1}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:10.937,结束时间:2020-07-27 17:10:11.945",
"第{2}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:10.939,结束时间:2020-07-27 17:10:11.940",
"第{3}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:10.938,结束时间:2020-07-27 17:10:11.940",
"第{4}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:10.939,结束时间:2020-07-27 17:10:11.941",
"第{5}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:11.943,结束时间:2020-07-27 17:10:12.944",
"第{6}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:11.944,结束时间:2020-07-27 17:10:12.944",
"第{7}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:11.944,结束时间:2020-07-27 17:10:12.945",
"第{8}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:11.945,结束时间:2020-07-27 17:10:12.946",
"第{9}个异步调用asyncsleepawhile方法:开始时间:2020-07-27 17:10:11.946,结束时间:2020-07-27 17:10:12.946"],
"消耗时间":"任务执行成功,耗时{2023}毫秒"}

这里大家注意思考一下,每个线程sleep了一秒,

我们启用了10个线程,为什么用的时间是2秒?

原因在于我们对线程池中核心线程数量的设置:可以并发执行5个线程,

所以10个线程共执行了两次,每次5个,每次都是1秒,两次用时2秒

六,查看spring boot版本:

  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.1.RELEASE)

spring boot:使用async异步线程池发送注册邮件(spring boot 2.3.1)的更多相关文章

  1. Spring Boot系列二 Spring @Async异步线程池用法总结

    1. TaskExecutor Spring异步线程池的接口类,其实质是java.util.concurrent.Executor Spring 已经实现的异常线程池: 1. SimpleAsyncT ...

  2. springboot+@async异步线程池的配置及应用

    示例: 1. 配置 @EnableAsync @Configuration public class TaskExecutorConfiguration { @Autowired private Ta ...

  3. spring boot / cloud (四) 自定义线程池以及异步处理@Async

    spring boot / cloud (四) 自定义线程池以及异步处理@Async 前言 什么是线程池? 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线 ...

  4. Spring集成JavaMail并利用线程池发送邮件

    我们系统存在大量发送邮件的需求,项目使用的是Spring框架而JavaMail也能很好的跟Spring进行集成,由于发送邮件最好还是使用异步进行发送,所以这里就采用线程池+JavaMail进行邮件发送 ...

  5. SpringBoot使用异步线程池实现生产环境批量数据推送

    前言 SpringBoot使用异步线程池: 1.编写线程池配置类,自定义一个线程池: 2.定义一个异步服务: 3.使用@Async注解指向定义的线程池: 这里以我工作中使用过的一个案例来做描述,我所在 ...

  6. 使用C++11实现一个半同步半异步线程池

    前言 C++11之前我们使用线程需要系统提供API.posix线程库或者使用boost提供的线程库,C++11后就加入了跨平台的线程类std::thread,线程同步相关类std::mutex.std ...

  7. 【SSM Spring 线程池 OJ】 使用Spring线程池ThreadPoolTaskExecutor

    最近做的Online Judge项目,在本地判题的实现过程中,遇到了一些问题,包括多线程,http通信等等.现在完整记录如下: OJ有一个业务是: 用户在前端敲好代码,按下提交按钮发送一个判题请求给后 ...

  8. 使用C++11 开发一个半同步半异步线程池

    摘自:<深入应用C++11>第九章 实际中,主要有两种方法处理大量的并发任务,一种是一个请求由系统产生一个相应的处理请求的线程(一对一) 另外一种是系统预先生成一些用于处理请求的进程,当请 ...

  9. c++11 实现半同步半异步线程池

    感受: 随着深入学习,现代c++给我带来越来越多的惊喜- c++真的变强大了. 半同步半异步线程池: 事实上非常好理解.分为三层 同步层:通过IO复用或者其它多线程多进程等不断的将待处理事件加入到队列 ...

随机推荐

  1. 初识ABP vNext(9):ABP模块化开发-文件管理

    Tips:本篇已加入系列文章阅读目录,可点击查看更多相关文章. 目录 前言 开始 创建模块 模块开发 应用服务 运行模块 单元测试 模块使用 最后 前言 在之前的章节中介绍过ABP扩展实体,当时在用户 ...

  2. oracle之二数据库审计

    数据库审计audit(PPT-I-320-334) 13.1 审计的功能:监控特定用户在database 的action(操作) 13.2 审计种类: 1)标准数据库审计(语句审计.权限审计.对象审计 ...

  3. Centos7源码编译安装LAMP环境

    参考地址:https://www.linuxidc.com/Linux/2018-03/151133.htm

  4. 【知识分享】Navicat Premium for Mac的破解教程

    转自Navicat Premium for Mac v12.0.22.0 破解教程,macOS上手动破解,无需补丁,无毒下载了Navicat,没有注册码,突然发现了这篇破解教程,竟爱不释手,顾Copy ...

  5. java Synchronized集合

    在Collections存在相关"Synchronized"支持同步的集合, 在java1.0 也存在"Vector"; 为什么会选择放弃"Vecto ...

  6. domReady的理解

    domReady的理解 domReady是名为DOMContentLoaded事件的别称,当初始的HTML文档被完全加载和解析完成之后,DOMContentLoaded事件被触发,而无需等待样式表.图 ...

  7. ES6 常用总结——第三章(数组、函数、对象的扩展)

    1.1. Array.from() Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结 ...

  8. Java11新特性

    局部变量类型推断增强 Java11中可以在lambda表达式的形参中使用var,好处是可以在形参上加注解 使用示例 (@Deprecated var x, @Nullable var y)->x ...

  9. Shiro性能优化:解决Session频繁读写问题

    目录 背景 应对思路 本地缓存 Session 避免不必要的 Session 更新 代码实现 ShiroSessionDAO.java ShiroConfig.java 背景 Shiro 提供了强大的 ...

  10. Java程序运行内存机制

    Java程序运行内存机制 栈内存包留调用方法.变量的区域,堆内存是new对象的区域,方法区为保存class文件的区域. 程序刚开始时,先加载类文件相应的数据到方法区,然后就从main()方法开始执行. ...