携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情
1. 结合CompletableFuture使用线程池
CompletableFuture,结合了Future的优点,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。
CompletableFuture可以传入自定义线程池,否则使用自己默认的线程池,我们习惯做法是自定义线程池,控制整个项目的线程数量,不使用自定义的线程池,做到可控可调
步骤一:声明一个线程池Bean
application.properties
//尽量做到每个业务使用自己配置的线程池
service1.thread.coreSize=10
service1.thread.maxSize=100
service1.thread.keepAliveTime=10
线程池的配置属性类
/**
* @date: 2022/8/12
* @FileName: ThreadPoolConfigProperties
* @author: Yan
* @Des:
*/
@ConfigurationProperties(prefix = "service1.thread")
@Data
public class ThreadPoolConfigProperties {
private Integer coreSize;
private Integer maxSize;
private Integer keepAliveTime;
}
@ConfigurationProperties
注解出现Spring Boot Configuration Annotation Processor not configured提示,若后续运行出错,可以进行如下操作参考官方文档
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
<project> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
把工程clean一下,然后重新build一下就ok了
步骤二:线程池的配置类config
线程池配置类: 根据不同业务定义不同的线程池配置
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @date: 2022/8/12
* @FileName: MyService1ThreadConfig
* @author: Yan
* @Des: 线程池配置类:根据不同业务定义不同的线程池配置
*/
@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
@Configuration
public class MyService1ThreadConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties pool) {
return new ThreadPoolExecutor(
pool.getCoreSize(),
pool.getMaxSize(),
pool.getKeepAliveTime(),
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(100000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
}
}
步骤三:使用
@Autowired
private ThreadPoolExecutor threadPoolExecutor;
@Test
void contextLoads() {
System.out.println("main.................start.....");
CompletableFuture.runAsync(() -> {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
}, threadPoolExecutor);
System.out.println("main.................end......");
}
结果
main.................start.....
main.................end......
当前线程:16
运行结果:5
2. 结合@Async使用线程池
在现实的互联网项目开发中,针对高并发的请求,一般的做法是高并发接口单独线程池隔离处理。可能为某一高并发的接口单独一个线程池
2.1 方式1:默认线程池
使用@Async注解,在默认情况下用的是SimpleAsyncTaskExecutor线程池,该线程池不是真正意义上的线程池
使用此线程池无法实现线程重用,每次调用都会新建一条线程。若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError
错误
步骤1: 自定义一个能查看线程池参数的类
- 不清楚线程池当时的情况,有多少线程在执行,多少在队列中等待呢?
- 创建了一个ThreadPoolTaskExecutor的子类,在每次提交线程任务的时候都会将当前线程池的运行状况打印出来
public class VisiableThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
private static final Logger logger = LoggerFactory.getLogger(VisiableThreadPoolTaskExecutor.class);
private void showThreadPoolInfo(String prefix) {
ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();
if (null == threadPoolExecutor) {
return;
}
logger.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",
this.getThreadNamePrefix(),
prefix,
threadPoolExecutor.getTaskCount(),
threadPoolExecutor.getCompletedTaskCount(),
threadPoolExecutor.getActiveCount(),
threadPoolExecutor.getQueue().size());
}
@Override
public void execute(Runnable task) {
showThreadPoolInfo("1. do execute");
super.execute(task);
}
@Override
public void execute(Runnable task, long startTimeout) {
showThreadPoolInfo("2. do execute");
super.execute(task, startTimeout);
}
@Override
public Future<?> submit(Runnable task) {
showThreadPoolInfo("1. do submit");
return super.submit(task);
}
@Override
public <T> Future<T> submit(Callable<T> task) {
showThreadPoolInfo("2. do submit");
return super.submit(task);
}
@Override
public ListenableFuture<?> submitListenable(Runnable task) {
showThreadPoolInfo("1. do submitListenable");
return super.submitListenable(task);
}
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
showThreadPoolInfo("2. do submitListenable");
return super.submitListenable(task);
}
}
步骤2: 实现AsyncConfigurer类
- 要配置默认的线程池,要实现
AsyncConfigurer
类的两个方法 - 不需要打印运行状况的可以使用ThreadPoolTaskExecutor类构建线程池
/**
* @date: 2022/8/16
* @FileName: AsyncThreadConfig
* @author: Yan
* @Des:
*/
@Slf4j
@EnableAsync
@Configuration
public class AsyncThread1Config implements AsyncConfigurer {
/**
* 定义@Async默认的线程池
* ThreadPoolTaskExecutor不是完全被IOC容器管理的bean,可以在方法上加上@Bean注解交给容器管理,
* 这样可以将taskExecutor.initialize()方法调用去掉,容器会自动调用
*
* @return
*/
@Override
public Executor getAsyncExecutor() {
int processors = Runtime.getRuntime().availableProcessors();
//常用的执行器
//ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//可以查看线程池参数的自定义执行器
ThreadPoolTaskExecutor taskExecutor = new VisiableThreadPoolTaskExecutor();
//核心线程数
taskExecutor.setCorePoolSize(1);
taskExecutor.setMaxPoolSize(2);
//线程队列最大线程数,默认:50
taskExecutor.setQueueCapacity(50);
//线程名称前缀
taskExecutor.setThreadNamePrefix("default-ljw-");
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化(重要)
taskExecutor.initialize();
return taskExecutor;
}
/**
* 异步方法执行的过程中抛出的异常捕获
*
* @return
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) ->
log.error("线程池执行任务发送未知错误,执行方法:{}", method.getName(), ex.getMessage());
}
}
步骤3:使用
在Service层对特定的需要异步处理的操作,直接添加注解
@Async
即可使用到配置的默认线程池
/**
* @date: 2022/8/16
* @FileName: ThreadAsyncService
* @author: Yan
* @Des:
*/
@Service
@Slf4j
public class ThreadAsyncService {
/**
* 默认线程池
*/
@SneakyThrows
@Async
public void defaultThread(){
long start = System.currentTimeMillis();
Random random = new Random();
System.out.println(99999999 * 99999999);
long end = System.currentTimeMillis();
// 抛出异常
int i = 1 / 0;
log.info("使用默认线程池,耗时:" + (end - start) + "毫秒");
}
}
测试类 ,调用异步处理
@Autowired
private ThreadAsyncService threadService;
@Test
void contextLoads() throws Exception {
System.out.println("main.................start.....");
threadService.defaultThread();
System.out.println("main.................end......");
}
运行结果
main.................start.....
2022-08-16 10:23:08.973 INFO 6680 --- [ main] c.y.t.e.VisiableThreadPoolTaskExecutor : Initializing ExecutorService
2022-08-16 10:23:08.974 INFO 6680 --- [ main] c.y.t.e.VisiableThreadPoolTaskExecutor : default-ljw-, 2. do submit,taskCount [0], completedTaskCount [0], activeCount [0], queueSize [0]
main.................end......
1674919425
2022-08-16 10:23:08.982 ERROR 6680 --- [ default-ljw-1] c.y.t.config.AsyncThread1Config : 线程池执行任务发送未知错误,执行方法:defaultThread
可以看到有线程池的相关记录**default-ljw-, 2. do submit,taskCount [0], completedTaskCount [0], activeCount [0], queueSize [0]
打印了出来,相关意思可以看步骤1**的VisiableThreadPoolTaskExecutor类的方法
2.2 方式2:指定线程池
由于业务需要,根据业务不同需要不同的线程池
在方式一的基础上进行下面步骤
步骤1:声明一个线程池bean
此时,项目中有两个线程池Bean,一个是方式一声明的AsyncThread1Config,一个是现在这个AsyncThread2Config
/**
* @date: 2022/8/16
* @FileName: AsyncThread2Config
* @author: Yan
* @Des: 注解@async配置
*/
@Slf4j
@EnableAsync
@Configuration
public class AsyncThread2Config {
@Bean("service2Executor")
public Executor service2Executor() {
//Java虚拟机可用的处理器数
int processors = Runtime.getRuntime().availableProcessors();
//定义线程池
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//可以查看线程池参数的自定义执行器
//ThreadPoolTaskExecutor taskExecutor = new VisiableThreadPoolTaskExecutor();
//核心线程数
taskExecutor.setCorePoolSize(processors);
taskExecutor.setMaxPoolSize(100);
//线程队列最大线程数,默认:100
taskExecutor.setQueueCapacity(100);
//线程名称前缀
taskExecutor.setThreadNamePrefix("my-ljw-");
//线程池中线程最大空闲时间,默认:60,单位:秒
taskExecutor.setKeepAliveSeconds(60);
//核心线程是否允许超时,默认:false
taskExecutor.setAllowCoreThreadTimeOut(false);
//IOC容器关闭时是否阻塞等待剩余的任务执行完成,默认:false(必须设置setAwaitTerminationSeconds)
taskExecutor.setWaitForTasksToCompleteOnShutdown(false);
//阻塞IOC容器关闭的时间,默认:10秒(必须设置setWaitForTasksToCompleteOnShutdown)
taskExecutor.setAwaitTerminationSeconds(10);
/**
* 拒绝策略,默认是AbortPolicy
* AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
* DiscardPolicy:丢弃任务但不抛出异常
* DiscardOldestPolicy:丢弃最旧的处理程序,然后重试,如果执行器关闭,这时丢弃任务
* CallerRunsPolicy:执行器执行任务失败,则在策略回调方法中执行任务,如果执行器关闭,这时丢弃任务
*/
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return taskExecutor;
}
}
步骤2:使用
@Async("service2Executor")注解指定使用的线程池名称
@Autowired
private ThreadAsyncService threadService;
@Test
void contextLoads() throws Exception {
System.out.println("main.................start.....");
threadService.defaultThread();
threadService.service2Executor();
System.out.println("main.................end......");
}
结果
2.3 @Async+Future获取异步执行结果
异步方法
/**
* 如果有返回值,返回类型应该为 Future<>
*
* @return
*/
@Async
public Future<String> getResult() throws InterruptedException {
Thread.sleep(3 * 1000);
return new AsyncResult<>("haha");
}
调用
log.info("异步获取返回值");
Future<String> result = service2.getResult();
//阻塞获取
String s = result.get();