SpringBoot自定义线程池(9)

前言

        实际项目中必不可少的会使用到异步处理,典型的比如注册后的短信通知或者邮件进行账户激活等。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>&nbsp&nbsp&nbsp&nbsp恭喜您注册账号成功!</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,细心的伙伴肯定发现了线程名称前缀正式我在配置类中定义的名称,这样方便我们进行日志阅读和问题排查。

 邮件信息

        查看邮箱,邮件发送成功,非常巴适非常奈斯。

上文只是使用邮件、短信进行案例演示,请根据自己项目实际生产情况编写自定义线程池。

如果有学到,就点个赞吧,一起进步。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值