前言
实际项目中必不可少的会使用到异步处理,典型的比如注册后的短信通知或者邮件进行账户激活等。Java可以用4种方式来创建线程,继承Thread类、实现Runnable接口、使用Callable和Future、使用线程池例如用Executor。有时候项目中可能有多个不同的业务类型都需要使用到异步线程,这时候我们可以根据不同的业务创建不同的线程池,让单一线程池专一做一件事情,分工明确,方便管理,本文就是介绍这种自定义线程池的创建方式。
线程池配置文件
为了配置文件清爽,单独分出一个配置文件,专门配置各种线程池,application-thread.yml 配置如下,本文就以异步发送短信和邮件为例,配置两个线程池,实际配置数据请根据自己项目情况进行调整:
# mail线程池配置
mail-thread-pool:
corePoolSize: 20 #核心线程数
maxPoolSize: 40 #最大线程数
keepAliveSeconds: 60 #空闲线程存活时间
queueCapacity: 1000 #等待队列大小
# message线程池配置
message-thread-pool:
corePoolSize: 20
maxPoolSize: 40
keepAliveSeconds: 60
queueCapacity: 1000
然后在application-thread.yml 主配置文件中引用上面配置:
spring:
profiles:
include: #不能换行, 多个用英文逗号分隔
ds,csdn,em,thread
线程池常量类
定义一个ThreadPoolConstants.java父类pojo常量类封装线程池配置信息:
package com.liberal.common.config.thread;
public class ThreadPoolConstants {
//核心线程数
private Integer corePoolSize = 8;
//最大线程数
private Integer maxPoolSize = 16;
//等待队列长度
private Integer queueCapacity = 128;
//空闲线程存活时间
private Integer keepAliveSeconds = 60;
public Integer getCorePoolSize() {
return corePoolSize;
}
public void setCorePoolSize(Integer corePoolSize) {
this.corePoolSize = corePoolSize;
}
public Integer getMaxPoolSize() {
return maxPoolSize;
}
public void setMaxPoolSize(Integer maxPoolSize) {
this.maxPoolSize = maxPoolSize;
}
public Integer getKeepAliveSeconds() {
return keepAliveSeconds;
}
public void setKeepAliveSeconds(Integer keepAliveSeconds) {
this.keepAliveSeconds = keepAliveSeconds;
}
public Integer getQueueCapacity() {
return queueCapacity;
}
public void setQueueCapacity(Integer queueCapacity) {
this.queueCapacity = queueCapacity;
}
}
定义邮件pojo常量类MailThreadPoolConstants.java继承ThreadPoolConstants.java用来封装邮件线程池配置:
package com.liberal.common.config.thread;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "mail-thread-pool")
public class MailThreadPoolConstants extends ThreadPoolConstants {
}
定义短信pojo常量类MessageThreadPoolConstants.java继承ThreadPoolConstants.java用来封装邮件线程池配置:
package com.liberal.common.config.thread;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "message-thread-pool")
public class MessageThreadPoolConstants extends ThreadPoolConstants {
}
线程池配置类
编写自定义线程池配置类ThreadPoolConfig.java,其中@EnableAsync注释可以在配置类,也可以在启动类,二选一,不可少。可以指定线程名称,区分业务,方便日志排查,指定拒绝策略,详细配置如下:
package com.liberal.common.config.thread;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class ThreadPoolConfig {
private final MailThreadPoolConstants mailThreadPoolConstants;
private final MessageThreadPoolConstants messageThreadPoolConstants;
@Autowired
public ThreadPoolConfig(MailThreadPoolConstants mailThreadPoolConstants, MessageThreadPoolConstants messageThreadPoolConstants) {
this.mailThreadPoolConstants = mailThreadPoolConstants;
this.messageThreadPoolConstants = messageThreadPoolConstants;
}
//邮件异步执行类
@Bean(name = "mailThreadExecutor")
public Executor getMailThreadExecutor() {
return initExecutor(mailThreadPoolConstants, "mailThreadExecutor-");
}
//短信异步执行类
@Bean(name = "messageThreadExecutor")
public Executor getMessageThreadExecutor() {
return initExecutor(messageThreadPoolConstants, "messageThreadExecutor-");
}
private ThreadPoolTaskExecutor initExecutor(ThreadPoolConstants constants, String prefix) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(constants.getCorePoolSize());
executor.setMaxPoolSize(constants.getMaxPoolSize());
executor.setKeepAliveSeconds(constants.getKeepAliveSeconds());
executor.setQueueCapacity(constants.getQueueCapacity());
// 设置默认线程名称
executor.setThreadNamePrefix(prefix);
// 设置拒绝策略rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
return executor;
}
}
异步操作示例
直接使用Spring提供的@Async注解就可以实现异步效果,但这种方式非常消耗cpu资源,实际开发禁止使用,解决途径就是使用该注解配合自定义的线程池, 其中EmailUtil类请看该文提取。
package com.liberal.async;
import com.liberal.common.bean.email.EmailBean;
import com.liberal.common.bean.email.EmailUser;
import com.liberal.common.utils.email.EmailUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @describe:@Async注解开启异步线程示例
* @author: weny.yang
* @date: 2021-08-22 13:53
*/
@Slf4j
@Component
public class AsyncTask {
//@Async 这种方式非常消耗cpu资源,实际开发禁止使用
@Async("messageThreadExecutor") //调用自定义线程池,中小型项目推荐使用,大型项目推荐使用MQ
public void sendMessage() throws InterruptedException {
log.info("异步发送短信开始...");
Thread.sleep(3000);
log.info("异步发送短信结束...");
}
@Async("mailThreadExecutor")
public void sendEmail() {
EmailBean bean = new EmailBean();
bean.setSmtpHost("smtp.qq.com");//此处测试qq服务器,其他邮箱类似,如126邮箱:smtp.126.com
bean.setEmailTitle("注册成功提醒");
bean.setEmailContent("<div>您好,</div><br/><div>    恭喜您注册账号成功!</div>");
// 发件人
bean.setFromUser(new EmailUser("Itelligence", "factorycode@foxmail.com", "xxxxxxxxx"));//昵称、邮箱地址、发件人stmp授权码
// 收件人
ArrayList<EmailUser> toList = new ArrayList<>();
toList.add(new EmailUser("factorycode@sina.com"));
bean.setRecipientType_TO(toList);
// 抄送人
ArrayList<EmailUser> ccList = new ArrayList<>();
ccList.add(new EmailUser("weny.yang@sun***.com"));
bean.setRecipientType_CC(ccList);
// 密送人
ArrayList<EmailUser> bccList = new ArrayList<>();
bccList.add(new EmailUser("factorycode@foxmail.com"));
bean.setRecipientType_BCC(bccList);
// 附件
List<String> annexPaths = new ArrayList<>();
annexPaths.add("/Users/ywy/IdeaProjects/Intelligence/HELP.md");
bean.setAnnexPaths(annexPaths);
EmailUtil.sendEmail(bean);
}
}
调用类
package com.liberal.controller;
import com.liberal.async.AsyncTask;
import com.liberal.common.bean.Response;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @describe:第一个RESTful接口示例
* @author: weny.yang
* @date: 2021-08-22 10:26
*/
@Slf4j
@RestController
public class HelloWorldController {
@Resource
private AsyncTask syncTask;
@RequestMapping("/register")
public Response register() throws Exception {
//模拟注册
//Do something
log.info("注册成功");
//异步发送短信邮件
syncTask.sendMessage();
syncTask.sendEmail();
return Response.success("注册成功!", null);
}
}
启动测试
启动项目,浏览器模拟注册,瞬间返回处理结果,无需等待发送完短信和邮件,用户体验爽歪歪:
再来看看控制台信息,根据日志打印时间顺序可知,同步方法执行完立马响应了浏览器,异步执行部分分别开启了两个独立的线程执行,线程名称分别是messageThreadExecutor-1、mailThreadExecutor-1,细心的伙伴肯定发现了线程名称前缀正式我在配置类中定义的名称,这样方便我们进行日志阅读和问题排查。
邮件信息
查看邮箱,邮件发送成功,非常巴适非常奈斯。
上文只是使用邮件、短信进行案例演示,请根据自己项目实际生产情况编写自定义线程池。
如果有学到,就点个赞吧,一起进步。