SpringBoot系列(15): 线程池-多线程Executors并发编程之广播式发送邮件(通知)


作者: 修罗debug
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

摘要:一直都想撸一撸Java中线程池、多线程并发编程的东西,但却苦于朝9晚9的苦逼日子迟迟木有动工,趁这会儿空闲,Debug将采用2篇文章来分享介绍、并采用代码实战关于“Java线程池、多线程并发编程”的实际应用场景!让各位小伙伴体验体验Java中线程池、多线程并发编程的魅力,本文我们将首先以“广播式发送邮件(通知)”为案例进行实战!

内容:对于Java中的线程池、多线程并发编程,相信各位小伙伴都有所耳闻,也大概知晓Java中的几种线程池(即Executors下的那几种),然而在实际的企业级项目业务模块开发中,有些小伙伴总是反应“多线程并发编程”不知道该应用在何处,不知道如何将Executors下的线程池应用到实际的业务场景下,于是乎,就有了本篇文章和下一篇文章!

“多线程编程”其实是相对于“单一线程编程”而言的,主要的作用当然是提高执行效率、系统的吞吐量,因为我们都知道一般一台服务器(或者你自己的电脑),不止1,一般低配的也要2核,好一点就是4核、8核等等。

有1核意味着将可以分配得到1个线程,而1个线程自然而然是用来处理每个请求、执行系统中几乎所有的每个任务,多核自然就意味着可以分配得道多个线程、从而处理多任务、多请求(我们的操作系统OS就是拥有这种特性),而这一点正是我们可以在应用系统中使用“多线程”的原因!

而线程池,顾名思义,就是一个“池”,里面会预先存放N个线程,当需要分配线程执行任务、执行请求时,会优先到“线程池”中获取,然后执行任务,完了之后将放回到“池”中去,省去了每次使用时创建、不用时销毁所带来的资源开销(主要是内存啦!)

下面,我们采用Java中提供的线程池进行多线程并发编程,以“广播式给用户发送邮件或者通知”为实战场景进行代码实战。

(1)既然是给用户“发送邮件”,那“邮箱”字段是少不了了,下面在用户user表中加入“邮箱”字段,其完整的DDL如下所示:

CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '名字',
`code` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '工号',
`email` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息表';

然后,逆向工程生成对应的Entity、Mapper、Mapper.xml等相应实体类,等待着被相应的类所使用!

(2)紧接着,我们建立一个ThreadController控制器,并在其中创建“广播式发送邮件”请求 对应的方法,如下所示:

@RestController
@RequestMapping("thread")
public class ThreadController extends AbstractController{

@Autowired
private ThreadService threadService;

@RequestMapping(value = "all/mail/send",method = RequestMethod.GET)
public BaseResponse sendAllUerEmail(){
BaseResponse response=new BaseResponse(StatusCode.Success);
try {
threadService.sendAllEmailsV1();

}catch (Exception e){
response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
}
return response;
}
}

 (3)在ThreadService中实现“广播式发送邮件”的核心业务逻辑,其中,我们采用的是FixedThreadPool线程池,预开设了10个线程用于给指定的用户发送邮件!  

@Service
public class ThreadService {
private static final Logger log= LoggerFactory.getLogger(ThreadService.class);

@Autowired
private UserMapper userMapper;

@Autowired
private EmailSendService emailSendService;

//TODO:给所有用户群发一封通知邮件 - 多线程(可以拓展的案例:WebSocket场景下给所有在线的用户发送通知消息)
public void sendAllEmailsV1() throws Exception{
Set<String> set=userMapper.selectAllUserEmails();
log.info("----给所有用户发送一封通知邮件,用户列表:{}",set);

if (set!=null && !set.isEmpty()){
//TODO:多线程并发-广播式发送邮件
ExecutorService executorService=Executors.newFixedThreadPool(10);

List<ThreadEmailDto> list= Lists.newLinkedList();
set.forEach(s -> list.add(new ThreadEmailDto(s,"双11课程优惠!","所有课程在关注公众号后可享受优惠价",emailSendService)));

executorService.invokeAll(list);
}
}
}

其中,“发送邮件”的真正核心业务逻辑(或者是“任务”)是在线程ThreadEmailDto中实现的,创建  每个用户邮箱 相应的ThreadEmailDto实例,然后放到列表,最终交给executorService.invokeAll(list); 执行 即可触发多线程情况并发广播式发送邮件的功能。其完整源代码如下所示:  

/**
* @Author:debug (SteadyJack)
* @Link: weixin-> debug0868 qq-> 1948831260
* @Date: 2019/11/8 10:18
**/
public class ThreadEmailDto implements Callable<Boolean>{
private String userEmail;
private String subject;
private String content;

private EmailSendService emailSendService;

public ThreadEmailDto(String userEmail, String subject, String content, EmailSendService emailSendService) {
this.userEmail = userEmail;
this.subject = subject;
this.content = content;
this.emailSendService = emailSendService;
}

//TODO:线程的核心任务-即要做的事情
@Override
public Boolean call() throws Exception {
emailSendService.sendSimpleEmail(subject,content,userEmail);
return true;
}
}

其中,发送邮件的逻辑,采用的是spring-boot-starter-mail提供的JavaMailSender实现的,其完整源代码如下所示:  

@Service
public class EmailSendService {

private static final Logger log= LoggerFactory.getLogger(EmailSendService.class);

@Autowired
private Environment env;

@Autowired
private JavaMailSender mailSender;

//TODO:发送简单的邮件消息
public void sendSimpleEmail(final String subject,final String content,final String ... tos){
try {
SimpleMailMessage message=new SimpleMailMessage();
message.setSubject(subject);
message.setText(content);
message.setTo(tos);
message.setFrom(env.getProperty("mail.send.from"));
mailSender.send(message);

log.info("----发送简单的邮件消息完毕--->");
}catch (Exception e){
log.error("--发送简单的邮件消息,发生异常:",e.fillInStackTrace());
}
}
}

(4)当然啦,为了能使用其中的JavaMailSender组件,你需要在pom.xml加入相应依赖:  

        <!--email-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<version>1.5.7.RELEASE</version>
</dependency>

以及相应的发送邮件相关的配置是放在application.properties中的,如下所示:  

#邮件配置
spring.mail.host=smtp.qq.com
spring.mail.username=1974544863@qq.com
spring.mail.password=前往qq邮箱-账户-设置-申请一个开启smtp/pop3授权的授权码
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true

mail.send.from=1974544863@qq.com

(5)最后,当然是通过Postman进行一番测试,测试结果如下图所示:  


从该运行结果中可以看到,最终用户确确实实是可以收到邮件的,而且在后端相应的接口中“发送邮件”时并非只是在“单一线程发送”,而是分给了N个线程进行处理(目前N=10,只是在这里我只设置了3个用户,故而只需要3个子线程异步去处理即可)!

至此,关于“线程池-多线程并发编程实现广播式发送邮件”就已经实战完毕了,各位小伙伴可以参照着撸一撸。另外,值得一提的是,这种是实现方式以及业务场景在很多其他地方也很类似,比如“前后端单体WebSocket应用场景下给所有在线的用户发送通知消息”等等!

补充:

1、本文涉及到的相关的源代码可以到此地址,check出来进行查看学习:

https://gitee.com/steadyjack/SpringBootTechnology

2、目前Debug已将本文所涉及的内容整理录制成视频教程,感兴趣的小伙伴可以前往观看学习:https://www.fightjava.com/web/index/course/detail/5

3、关注一下Debug的技术微信公众号,最新的技术文章、课程以及技术专栏将会第一时间在公众号发布哦!