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线程池。