SpringBoot系列(15): 線程池-多線程Executors并發(fā)編程之廣播式發(fā)送郵件(通知)


作者: 修羅debug
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 by-sa 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明。

摘要:一直都想擼一擼Java中線程池、多線程并發(fā)編程的東西,但卻苦于朝9晚9的苦逼日子遲遲木有動工,趁這會兒空閑,Debug將采用2篇文章來分享介紹、并采用代碼實戰(zhàn)關(guān)于“Java線程池、多線程并發(fā)編程”的實際應用場景!讓各位小伙伴體驗體驗Java中線程池、多線程并發(fā)編程的魅力,本文我們將首先以“廣播式發(fā)送郵件(通知)”為案例進行實戰(zhàn)!

內(nèi)容:對于Java中的線程池、多線程并發(fā)編程,相信各位小伙伴都有所耳聞,也大概知曉Java中的幾種線程池(即Executors下的那幾種),然而在實際的企業(yè)級項目業(yè)務模塊開發(fā)中,有些小伙伴總是反應“多線程并發(fā)編程”不知道該應用在何處,不知道如何將Executors下的線程池應用到實際的業(yè)務場景下,于是乎,就有了本篇文章和下一篇文章!

“多線程編程”其實是相對于“單一線程編程”而言的,主要的作用當然是提高執(zhí)行效率、系統(tǒng)的吞吐量,因為我們都知道一般一臺服務器(或者你自己的電腦),不止1,一般低配的也要2核,好一點就是4核、8核等等。

有1核意味著將可以分配得到1個線程,而1個線程自然而然是用來處理每個請求、執(zhí)行系統(tǒng)中幾乎所有的每個任務,多核自然就意味著可以分配得道多個線程、從而處理多任務、多請求(我們的操作系統(tǒng)OS就是擁有這種特性),而這一點正是我們可以在應用系統(tǒng)中使用“多線程”的原因!

而線程池,顧名思義,就是一個“池”,里面會預先存放N個線程,當需要分配線程執(zhí)行任務、執(zhí)行請求時,會優(yōu)先到“線程池”中獲取,然后執(zhí)行任務,完了之后將放回到“池”中去,省去了每次使用時創(chuàng)建、不用時銷毀所帶來的資源開銷(主要是內(nèi)存啦?。?/span>

下面,我們采用Java中提供的線程池進行多線程并發(fā)編程,以“廣播式給用戶發(fā)送郵件或者通知”為實戰(zhàn)場景進行代碼實戰(zhàn)。

(1)既然是給用戶“發(fā)送郵件”,那“郵箱”字段是少不了了,下面在用戶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控制器,并在其中創(chuàng)建“廣播式發(fā)送郵件”請求 對應的方法,如下所示:

@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中實現(xiàn)“廣播式發(fā)送郵件”的核心業(yè)務邏輯,其中,我們采用的是FixedThreadPool線程池,預開設(shè)了10個線程用于給指定的用戶發(fā)送郵件!  

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

@Autowired
private UserMapper userMapper;

@Autowired
private EmailSendService emailSendService;

//TODO:給所有用戶群發(fā)一封通知郵件 - 多線程(可以拓展的案例:WebSocket場景下給所有在線的用戶發(fā)送通知消息)
public void sendAllEmailsV1() throws Exception{
Set<String> set=userMapper.selectAllUserEmails();
log.info("----給所有用戶發(fā)送一封通知郵件,用戶列表:{}",set);

if (set!=null && !set.isEmpty()){
//TODO:多線程并發(fā)-廣播式發(fā)送郵件
ExecutorService executorService=Executors.newFixedThreadPool(10);

List<ThreadEmailDto> list= Lists.newLinkedList();
set.forEach(s -> list.add(new ThreadEmailDto(s,"雙11課程優(yōu)惠!","所有課程在關(guān)注公眾號后可享受優(yōu)惠價",emailSendService)));

executorService.invokeAll(list);
}
}
}

其中,“發(fā)送郵件”的真正核心業(yè)務邏輯(或者是“任務”)是在線程ThreadEmailDto中實現(xiàn)的,創(chuàng)建  每個用戶郵箱 相應的ThreadEmailDto實例,然后放到列表,最終交給executorService.invokeAll(list); 執(zhí)行 即可觸發(fā)多線程情況并發(fā)廣播式發(fā)送郵件的功能。其完整源代碼如下所示:  

/**
* @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;
}
}

其中,發(fā)送郵件的邏輯,采用的是spring-boot-starter-mail提供的JavaMailSender實現(xiàn)的,其完整源代碼如下所示:  

@Service
public class EmailSendService {

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

@Autowired
private Environment env;

@Autowired
private JavaMailSender mailSender;

//TODO:發(fā)送簡單的郵件消息
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("----發(fā)送簡單的郵件消息完畢--->");
}catch (Exception e){
log.error("--發(fā)送簡單的郵件消息,發(fā)生異常:",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>

以及相應的發(fā)送郵件相關(guān)的配置是放在application.properties中的,如下所示:  

#郵件配置
spring.mail.host=smtp.qq.com
spring.mail.username=1974544863@qq.com
spring.mail.password=前往qq郵箱-賬戶-設(shè)置-申請一個開啟smtp/pop3授權(quán)的授權(quán)碼
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進行一番測試,測試結(jié)果如下圖所示:  


從該運行結(jié)果中可以看到,最終用戶確確實實是可以收到郵件的,而且在后端相應的接口中“發(fā)送郵件”時并非只是在“單一線程發(fā)送”,而是分給了N個線程進行處理(目前N=10,只是在這里我只設(shè)置了3個用戶,故而只需要3個子線程異步去處理即可)!

至此,關(guān)于“線程池-多線程并發(fā)編程實現(xiàn)廣播式發(fā)送郵件”就已經(jīng)實戰(zhàn)完畢了,各位小伙伴可以參照著擼一擼。另外,值得一提的是,這種是實現(xiàn)方式以及業(yè)務場景在很多其他地方也很類似,比如“前后端單體WebSocket應用場景下給所有在線的用戶發(fā)送通知消息”等等!

補充:

1、本文涉及到的相關(guān)的源代碼可以到此地址,check出來進行查看學習:

https://gitee.com/steadyjack/SpringBootTechnology

2、目前Debug已將本文所涉及的內(nèi)容整理錄制成視頻教程,感興趣的小伙伴可以前往觀看學習:https://www.fightjava.com/web/index/course/detail/5

3、關(guān)注一下Debug的技術(shù)微信公眾號,最新的技術(shù)文章、課程以及技術(shù)專欄將會第一時間在公眾號發(fā)布哦!