Spring Boot笔记—多线程系列(二)—自定义多线程

1 介绍

    自定义线程池,有两种实现方式:

  • 更改spring默认的线程池配置,全局生效

  • 自定义新的线程池,指定作用范围

    上一篇文章[《Spring Boot笔记-多线程系列(一)-使用多线程》](https://yxdz.top/2018/12/07/Spring Boot笔记-多线程系列(一)-使用多线程/)使用的就是spring默认的线程池。

2 实现

2.1 更改spring默认线程池配置

2.1.1 介绍

        创建一个类,需要实现AsyncConfigurer即可。

2.1.2 样例

2.1.2.1 目录结构

├── Study
│   ├── src
│   │   ├── main
│   │   │   ├── java
│   │   │   │   └── top
│   │   │   │       └── yxdz
│   │   │   │           └── study
│   │   │   │               └── spring
│   │   │   │                   └── springboot
│   │   │   │                       ├── thread
│   │   │   │                       │   └── service
│   │   │   │                       │       ├── ITestService.java
│   │   │   │                       │       └── impl
│   │   │   │                       │           └── TestSerivceImpl.java
│   │   │   │                       └── utils
│   │   │   │                           ├── SysThreadValueConfig.java
│   │   │   │                           └── config
│   │   │   │                               └── Thread
│   │   │   │                                   └── SysThreadConfig.java
│   │   │   └── resources
│   │   │       ├── application.yml
│   │   └── test
│   │       └── java
│   │           └── top
│   │               └── yxdz
│   │                   └── study
│   │                       └── StudyApplicationTests.java

2.1.2.2 代码实现

  • application.yml ——自定义了线程池的一些参数信息

    spring:
      thread:
        threadNamePrefix: system-thread #线程名称前缀
        corePoolSize: 20 #核心线程池大小
        maxPoolSize: 40 #最大线程数
        keepAliveSeconds: 300 #活跃时间(单位:秒)
        queueCapacity: 50 #队列容量
    
  • SysThreadValueConfig.java——获取application.yml的自定义线程参数信息

    package top.yxdz.study.spring.springboot.utils;
    
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    @Component
    @ConfigurationProperties(prefix = "spring.thread")
    public class SysThreadValueConfig {
    
        private String threadNamePrefix; //线程名称前缀
    
        private int corePoolSize; //核心线程数
    
        private int maxPoolSize; //最大线程数
    
        private int keepAliveSeconds; //线程存活时间
    
        private int queueCapacity; //队列容量
    
        public String getThreadNamePrefix() {
            return threadNamePrefix;
        }
    
        public void setThreadNamePrefix(String threadNamePrefix) {
            this.threadNamePrefix = threadNamePrefix;
        }
    
        public int getCorePoolSize() {
            return corePoolSize;
        }
    
        public void setCorePoolSize(int corePoolSize) {
            this.corePoolSize = corePoolSize;
        }
    
        public int getMaxPoolSize() {
            return maxPoolSize;
        }
    
        public void setMaxPoolSize(int maxPoolSize) {
            this.maxPoolSize = maxPoolSize;
        }
    
        public int getKeepAliveSeconds() {
            return keepAliveSeconds;
        }
    
        public void setKeepAliveSeconds(int keepAliveSeconds) {
            this.keepAliveSeconds = keepAliveSeconds;
        }
    
        public int getQueueCapacity() {
            return queueCapacity;
        }
    
        public void setQueueCapacity(int queueCapacity) {
            this.queueCapacity = queueCapacity;
        }
    }
    
  • SysThreadConfig.java——更改spring默认的线程池配置类

            将application.yml文件中的自定义参数信息读取,配置到响应位置。

    package top.yxdz.study.spring.springboot.utils.config.Thread;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.annotation.AsyncConfigurer;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    import top.yxdz.study.spring.springboot.utils.SysThreadValueConfig;
    
    import java.lang.reflect.Method;
    import java.util.concurrent.Executor;
    import java.util.concurrent.ThreadPoolExecutor;
    
    /**
     * spring默认的线程池装配类
     */
    @Configuration
    public class SysThreadConfig implements AsyncConfigurer {
    
        private static Logger LOG = LoggerFactory.getLogger(SysThreadConfig.class);
    
        @Autowired
        SysThreadValueConfig sysValueConfig;
    
        @Override
        public Executor getAsyncExecutor(){
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    
            //核心线程池大小
            executor.setCorePoolSize(sysValueConfig.getCorePoolSize());
            //最大线程数
            executor.setMaxPoolSize(sysValueConfig.getMaxPoolSize());
            //队列容量
            executor.setQueueCapacity(sysValueConfig.getQueueCapacity());
            //活跃时间
            executor.setKeepAliveSeconds(sysValueConfig.getKeepAliveSeconds());
            //线程名字前缀
            executor.setThreadNamePrefix(sysValueConfig.getThreadNamePrefix());
            //线程池满的时候,处理新任务的策略
            //CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    
            //线程实例化
            executor.initialize();
    
            return executor;
        }
    
        @Override
        public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler(){
            return new AsyncUncaughtExceptionHandler() {
                @Override
                public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
                    LOG.error("异步任务失败:失败方法【{}】,失败日志【{}】" , method.getName(), throwable.getMessage());
                }
            };
        }
    }
    
  • StudyApplicationTests.java——测试启动的入口

            内容同Spring Boot笔记—多线程系列(一)—使用多线程一样,未修改。

    package top.yxdz.study;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.scheduling.annotation.EnableAsync;
    import org.springframework.test.context.junit4.SpringRunner;
    import top.yxdz.study.spring.springboot.thread.service.ITestService;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    @EnableAsync
    public class StudyApplicationTests {
    
        @Autowired
        ITestService iTestService;
    
        @Test
        public void contextLoads() {
    
            for(int i=0; i<5; i++){
                iTestService.method1("Async" + i);
            }
    
            try {
                //等待10s,防止异步代码被强制关闭导致线程抛出异常
                Thread.sleep(10000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    
  • ITestService.java

            内容同Spring Boot笔记—多线程系列(一)—使用多线程一样,未修改。

    package top.yxdz.study.spring.springboot.thread.service;
    
    public interface ITestService {
    
        /**
         * 异步测试
         * @param msg
         */
        void method1(String msg);
    }
    
  • TestSerivceImpl.java——测试类

            内容同Spring Boot笔记—多线程系列(一)—使用多线程一样,未修改。

    package top.yxdz.study.spring.springboot.thread.service.impl;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Service;
    import top.yxdz.study.spring.springboot.thread.service.ITestService;
    
    import java.time.LocalDateTime;
    
    
    @Service("TestSerivceImpl")
    public class TestSerivceImpl implements ITestService {
    
        private static Logger LOG = LoggerFactory.getLogger(TestSerivceImpl.class);
    
        @Override
        @Async
        public void method1(String msg){
            try {
                LOG.info(LocalDateTime.now().toString() + msg);
                Thread.sleep(1000);
                LOG.info(LocalDateTime.now().toString() + msg);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    

2.1.2.3 执行结果

  • 执行结果

    2018-12-14 15:41:53.985  INFO 9998 --- [ system-thread1] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:41:53.985Async0
    2018-12-14 15:41:53.985  INFO 9998 --- [ system-thread3] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:41:53.985Async2
    2018-12-14 15:41:53.985  INFO 9998 --- [ system-thread4] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:41:53.985Async3
    2018-12-14 15:41:53.985  INFO 9998 --- [ system-thread2] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:41:53.985Async1
    2018-12-14 15:41:53.985  INFO 9998 --- [ system-thread5] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:41:53.985Async4
    2018-12-14 15:41:54.990  INFO 9998 --- [ system-thread3] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:41:54.990Async2
    2018-12-14 15:41:54.990  INFO 9998 --- [ system-thread2] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:41:54.990Async1
    2018-12-14 15:41:54.990  INFO 9998 --- [ system-thread1] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:41:54.990Async0
    2018-12-14 15:41:54.990  INFO 9998 --- [ system-thread4] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:41:54.990Async3
    2018-12-14 15:41:54.990  INFO 9998 --- [ system-thread5] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:41:54.990Async4
    
  • 结果说明

            根据日志结果,线程名前缀都是system-thread,也就是我们自己自定义的名称前缀,说明修改默认配置参数信息成功。

2.2 自定义新的线程池

2.2.1 介绍

        自定义线程池与spring默认的线程池的差别在于:

  • 配置类,不需要实现AsyncConfigurer

  • 在使用注解@Async的时候,需要在后面添加("自定义线程池配置的bean名称")来指定使用哪个线程池

2.2.2 样例

2.2.2.1 目录结构

├── Study
│   ├── src
│   │   ├── main
│   │   │   ├── java
│   │   │   │   └── top
│   │   │   │       └── yxdz
│   │   │   │           └── study
│   │   │   │               └── spring
│   │   │   │                   └── springboot
│   │   │   │                       ├── thread
│   │   │   │                       │   └── service
│   │   │   │                       │       ├── ITestService.java #修改的文件
│   │   │   │                       │       └── impl
│   │   │   │                       │           └── TestSerivceImpl.java #修改的文件
│   │   │   │                       └── utils
│   │   │   │                           ├── SysThreadValueConfig.java
│   │   │   │                           └── config
│   │   │   │                               └── Thread
│   │   │   │                                   └── SysThreadConfig.java
│   │   │   │                                   └── SelfThreadConfig.java #增加的文件
│   │   │   └── resources
│   │   │       ├── application.yml
│   │   └── test
│   │       └── java
│   │           └── top
│   │               └── yxdz
│   │                   └── study
│   │                       └── StudyApplicationTests.java #修改的文件

2.2.2.2 代码实现

  • application.yml——未修改

  • SysThreadValueConfig.java——未修改

  • SelfThreadConfig.java——自定义线程池

            与更改spring默认线程池配置相比,不用实现AsyncConfigurer接口。

    package top.yxdz.study.spring.springboot.utils.config.Thread;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    import top.yxdz.study.spring.springboot.utils.SysThreadValueConfig;
    
    import java.util.concurrent.Executor;
    import java.util.concurrent.ThreadPoolExecutor;
    
    /**
     * 自定义线程池
     */
    @Configuration
    public class SelfThreadConfig {
        private static Logger LOG = LoggerFactory.getLogger(SelfThreadConfig.class);
    
        @Autowired
        SysThreadValueConfig sysValueConfig;
    
        @Bean
        public Executor myThreadPool(){
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    
            //核心线程池大小
            executor.setCorePoolSize(sysValueConfig.getCorePoolSize());
            //最大线程数
            executor.setMaxPoolSize(sysValueConfig.getMaxPoolSize());
            //队列容量
            executor.setQueueCapacity(sysValueConfig.getQueueCapacity());
            //活跃时间
            executor.setKeepAliveSeconds(sysValueConfig.getKeepAliveSeconds());
            //线程名字前缀
            executor.setThreadNamePrefix("my-thread-pool");
            //线程池满的时候,处理新任务的策略
            //CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    
            //线程实例化
            executor.initialize();
    
            return executor;
        }
    }
    
  • StudyApplicationTests.java——测试启动入口

    package top.yxdz.study;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.scheduling.annotation.EnableAsync;
    import org.springframework.test.context.junit4.SpringRunner;
    import top.yxdz.study.spring.springboot.thread.service.ITestService;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    @EnableAsync
    public class StudyApplicationTests {
    
        @Autowired
        ITestService iTestService;
    
        @Test
        public void contextLoads() {
    
            for(int i=0; i<5; i++){
                iTestService.method1("system" + i); //使用springn线程池
                iTestService.method2("myself" + i); //使用自定义线程池
            }
    
            try {
                //等待10s,防止异步代码被强制关闭导致线程抛出异常
                Thread.sleep(10000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    
    
  • ITestService.java

    package top.yxdz.study.spring.springboot.thread.service;
    
    public interface ITestService {
    
        /**
         * 异步测试
         * @param msg
         */
        void method1(String msg);
    
        /**
         * 异步测试
         * @param msg
         */
        void method2(String msg);
    }
    
  • TestSerivceImpl.java——实现类

            增加了新的函数,使用自定义线程池。使用自定义线程池,需要更改注解为@Async("myThreadPool"),用来表示使用自定义线程池

    package top.yxdz.study.spring.springboot.thread.service.impl;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Service;
    import top.yxdz.study.spring.springboot.thread.service.ITestService;
    
    import java.time.LocalDateTime;
    
    
    @Service("TestSerivceImpl")
    public class TestSerivceImpl implements ITestService {
    
        private static Logger LOG = LoggerFactory.getLogger(TestSerivceImpl.class);
    
        /**
         * 使用系统的线程池
         * @param msg
         */
        @Override
        @Async
        public void method1(String msg){
            try {
                LOG.info(LocalDateTime.now().toString() + msg);
                Thread.sleep(1000);
                LOG.info(LocalDateTime.now().toString() + msg);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    
        /**
         * 使用个自定义线程池
         * @param msg
         */
        @Override
        @Async("myThreadPool")
        public void method2(String msg){
            try {
                LOG.info(LocalDateTime.now().toString() + msg);
                Thread.sleep(1000);
                LOG.info(LocalDateTime.now().toString() + msg);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    

2.2.2.3 执行结果

  • 执行结果

    2018-12-14 15:56:38.514  INFO 10292 --- [my-thread-pool1] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:38.514myself0
    2018-12-14 15:56:38.514  INFO 10292 --- [my-thread-pool2] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:38.514myself1
    2018-12-14 15:56:38.514  INFO 10292 --- [ system-thread2] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:38.514system1
    2018-12-14 15:56:38.514  INFO 10292 --- [ system-thread5] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:38.514system4
    2018-12-14 15:56:38.514  INFO 10292 --- [my-thread-pool5] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:38.514myself4
    2018-12-14 15:56:38.514  INFO 10292 --- [ system-thread4] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:38.514system3
    2018-12-14 15:56:38.514  INFO 10292 --- [my-thread-pool3] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:38.514myself2
    2018-12-14 15:56:38.514  INFO 10292 --- [ system-thread1] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:38.514system0
    2018-12-14 15:56:38.514  INFO 10292 --- [my-thread-pool4] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:38.514myself3
    2018-12-14 15:56:38.514  INFO 10292 --- [ system-thread3] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:38.514system2
    2018-12-14 15:56:39.519  INFO 10292 --- [my-thread-pool1] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:39.519myself0
    2018-12-14 15:56:39.519  INFO 10292 --- [my-thread-pool4] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:39.519myself3
    2018-12-14 15:56:39.519  INFO 10292 --- [my-thread-pool5] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:39.519myself4
    2018-12-14 15:56:39.519  INFO 10292 --- [my-thread-pool2] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:39.519myself1
    2018-12-14 15:56:39.519  INFO 10292 --- [ system-thread1] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:39.519system0
    2018-12-14 15:56:39.519  INFO 10292 --- [my-thread-pool3] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:39.519myself2
    2018-12-14 15:56:39.519  INFO 10292 --- [ system-thread2] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:39.519system1
    2018-12-14 15:56:39.519  INFO 10292 --- [ system-thread4] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:39.519system3
    2018-12-14 15:56:39.519  INFO 10292 --- [ system-thread5] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:39.519system4
    2018-12-14 15:56:39.519  INFO 10292 --- [ system-thread3] t.y.s.s.s.t.s.impl.TestSerivceImpl       : 2018-12-14T15:56:39.519system2
    
  • 结果说明

            打印信息是myself为前缀的信息,使用的线程都是my-thread-pool开头,说明使用的是自定义线程池。

            打印信息是system为前缀的信息,使用的线程都是system-thread开头,说明使用的被重新配置的spring线程池。

3 参考文章

SpringBoot 自定义线程池

posted @ 2018-12-14 16:23  悠闲的宅  阅读(301)  评论(0编辑  收藏  举报