文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

深入浅出Java/Spring/Spring Boot异步多线程

2024-12-01 14:49

关注

线程池(绿色方块)/等待处理任务队列(蓝色)/处理完成任务(黄色)

该模式允许创建的线程数量及其生命周期。 我们还能够安排任务的执行并将传入的任务保持在队列(Task Queue)中。

线程池数量的大小可根据程序可用的计算资源进行调整,它通常是程序的可调参数,经过调整以优化程序的性能。 确定最佳线程池大小对于优化性能至关重要。

1.2 Java的线程池ThreadPoolExecutor

Java中的ThreadPoolExecutor是一个可扩展的线程池的实现,它提供了对很多参数的微调设置。这些参数包括:

ThreadPoolExecutor的构造器:

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
//...
}

示例:

ThreadPoolExecutor executorPool = new ThreadPoolExecutor(5, 10, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50));

corePoolSize:第1个参数“5”。JVM会为前5个任务创建线程,后续的任务会放进队列里,直到队列满为止(第5个参数workQueue,50个任务)。

maximumPoolSize:第2个参数“10”。JVM最多创建10个线程,这意味着当当前正有5个线程运行着5个任务,而这时任务队列的50个任务已满,此时如果有一个新的任务到达队列,JVM将创建一个新的线程,最多创建5个,直至10个。

keepAliveTime:第3个参数“3秒”,超出核心线程而小于最大线程的这些线程,在一定的空闲时间之后将被清除掉。

unit:第4个参数“秒”,keepAliveTime的时间单位。

workQueue:第5个参数“new ArrayBlockingQueue(50)”,任务队列的大小。

JVM创建线程的规则如下:

2、Spring的多线程

Spring/Spring Boot只需要在配置类上注解“@EnableAsync”,在需要使用单独线程的方法上使用“@Async”注解即可。Spring会自动检索线程池的定义,可以是“org.springframework.core.task.TaskExecutor”或者是“java.util.concurrent.Executor”的名为“taskExecutor”的bean。若都未找到,则使用“org.springframework.core.task.SimpleAsyncTaskExecutor”来处理异步方法的调用。

我们最简单可以通过自定一个名为“taskExecutor”的Bean即可。

@Configuration
@EnableAsync
class AsyncConfigurationByBean {
@Bean(name="taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("poolThread-");
executor.initialize();
return executor;
}

}

Spring也提供了“AsyncConfigurer”接口用来定制实现异步多线程相关的配置。

@Configuration
@EnableAsync
class AsyncConfigurationByConfigurer implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("poolThread-");
executor.initialize();
return executor;
}

@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return AsyncConfigurer.super.getAsyncUncaughtExceptionHandler();
}
}

3、Spring Boot的多线程

在Spring Boot下,通过“TaskExecutionAutoConfiguration”自动配置类,Spring Boot给我们已经自动配置好了线程池,“TaskExecutionProperties”提供了相关的属性配置。在Spring Boot下我们只需要在配置类上“@EnableAsync”,在“application.yml”上配置即可:

spring:
task:
execution:
pool:
core-size: 5
max-size: 10
queue-capacity: 50
thread-name-prefix: poolThread-

4、多线程演示

新建一个使用异步方法的类和方法:

@Service
@Slf4j
class AsyncService {
@Async
public void doSomething(Integer i){
log.info("当前是循环:" + i);
}
}

在Spring Boot入口类调用:

@SpringBootApplication
@EnableAsync
public class SpringAsyncApplication {

public static void main(String[] args) {
SpringApplication.run(SpringAsyncApplication.class, args);
}

@Bean
CommandLineRunner commandLineRunner(AsyncService asyncService){
return p -> {
for(int i = 0 ; i < 10 ; i ++){
asyncService.doSomething(i);
}
};
}
}

运行结果:

从图中可以看出我们的核心线程数量是5,你可以按照上节的“JVM创建线程的规则”来调整核心线程、最大线程、队列数量的来尝试一下观察控制台输出的结果。

5、多线程异步结果演示

我们有时候会在单一的请求中调用多个方法,在同步的方法里,我们都是顺序执行,执行完一个再执行下一个。我们可以通过Spring的“AsyncResult”让多个方法并发执行并聚合他们的结果,并提高性能。

我们先看一下,在同步的情况下是什么样的:

@Service
class Service1 {
public Integer doSomething() throws InterruptedException {
Thread.sleep(2000);
log.info("在Service1中");
return 1;
}
}
@Service
class Service2 {
public Integer doSomething() throws InterruptedException {
Thread.sleep(2000);
log.info("在Service2中");
return 2 ;
}
}
@Bean
CommandLineRunner commandLineRunner(Service1 service1, Service2 service2){
return p -> {
long start = System.currentTimeMillis();
Integer first = service1.doSomething();
Integer second = service2.doSomething();
Integer sum = first + second;
long end = System.currentTimeMillis();
Long cost = end - start;
log.info("结果为:" + sum + ",耗时" + cost);
};
}

示例中,一个线程顺序执行两个方法,执行结果为:

在一个线程中

我们现在使用“@Async”使方法变成异步,且使用“AsyncResult”包装异步结果返回。

“CompletableFuture”是Java8引入的,以提供一种编写异步、非阻塞和多线程代码的简单方法。我们使用“AsyncResult”包装返回值,并用它的“.completable()”方法获得“CompletableFuture”对象。

@Service
@Slf4j
class Service1 {
@Async
public CompletableFuture<Integer> doSomething() throws InterruptedException {
Thread.sleep(2000);
log.info("在Service1中");
return new AsyncResult<Integer>(1).completable();
}
}
@Service
@Slf4j
class Service2 {
@Async
public CompletableFuture<Integer> doSomething() throws InterruptedException {
Thread.sleep(2000);
log.info("在Service2中");
return new AsyncResult<Integer>(2).completable();
}
}
@Bean
CommandLineRunner commandLineRunner(Service1 service1, Service2 service2){
return p -> {
long start = System.currentTimeMillis();
CompletableFuture<Integer> firstData = service1.doSomething();
CompletableFuture<Integer> secondData = service2.doSomething();
CompletableFuture<Integer> mergeResult = firstData.thenCompose(
firstValue -> secondData.thenApply(
secondValue -> firstValue + secondValue
)
);
long end = System.currentTimeMillis();
Long cost = end - start;
log.info("结果为:" + mergeResult.get() + ",耗时" + cost);
};
}

本文转载自今日「爱科学的卫斯理」,可以通过以下二维码关注。转载本文请联系爱科学的卫斯理。

来源:今日头条内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯