对于我的研究,我正在研究SpringBoot REST API。我应该在收到请求时减少代码的执行时间。所以,我认为让代码异步是个好主意。但是,不幸的是,我在Spring方面遇到了一些问题,尽管我在网上花了几个小时研究寻找解决方案,但我什么也没找到。让我解释一下:
为了优化我的代码,我决定使用@Async
SpringAnnotation。为此,我创建了一个AsyncConfiguration类,它看起来像这样:
@Configuration
@EnableAsync
public class AsyncConfiguration {
@Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("AsynchThread-");
executor.initialize();
return executor;
}
}
通常,asyncExecutor()
方法返回Spring必须用于异步调用的Executor
类或子类。在这种情况下,它是一个ThreadPoolTaskExecutor
。我的大部分代码都用@Async
进行了注释,以使用我的asyncExecutor
,如下所示:
@Async("asyncExecutor")
public CompletableFuture<User> calculateRewards(User user) {
return CompletableFuture.supplyAsync(() -> {
logger.info("Calculating Reward for user : " + user.getUserName());
List<VisitedLocation> userLocations = user.getVisitedLocations();
List<Attraction> attractions = gps.getAllAttraction();
for(VisitedLocation visitedLocation : userLocations) {
for(Attraction attraction : attractions) {
if(user.getUserRewards().stream().filter(r -> r.attraction.attractionName.equals(attraction.attractionName)).count() == 0) {
if(nearAttraction(visitedLocation, attraction)) {
user.addUserReward(new UserReward(visitedLocation, attraction, reward.getAttractionRewardPoints(attraction.attractionId, user.getUserId())));
}
}
}
}
return user;
});
}
但是,重点是:当我运行代码时,Spring不要使用我的asyncExecutor()
bean。我怎么知道?首先,当我在终端中调用一个带注释的方法时,我看到的是:
2022-10-27 00:26:24.688 INFO 41436 --- [onPool-worker-4] c.T.T.service.TourGuideMainService : Calculating Reward for user : internalUser919
";[在Pool-worker-4]上]">是线程名称,或者至少是线程名称的末尾。但它不应该这样命名。如果你看看我上面的asyncExecutor()
方法,你可以看到有一个executor.setThreadNamePrefix("AsynchThread-");
。如果代码按预期工作,则线程应被称为"线程";异步线程-4";,但事实并非如此。
其次,我决定在调试模式下运行我的代码,然后进入VS代码的调试菜单,我发现了两件事:
1-当我运行我的压力测试,同时调用1000个calculateRewards()
时,只创建了11个线程。考虑到calculateRewards()
方法的执行时间以及Executor默认具有maxPoolSize
(即Integer.MAX_VALUE
(的事实,应该有11个以上的线程;
2-创建线程时,线程的整个名称为">";[FokJoinPool.commonPool-worker-4]">;
Spring似乎在使用ForkJoinPool
类来创建Threads,而且它从未考虑过我的Executor的配置。我不知道它为什么这么做,我从来没有使用过ForkJoinPool
,就像我说的,我在网上搜索时没有找到任何东西。
那么,为什么Spring在我的代码中使用ForkJoinPool
而不是ThreadPoolTaskExecutor
作为异步方法呢?最重要的是:我该如何解决这个问题?
(我希望这是可以理解的…(
编辑1:在一些随机测试中,我发现,如果Spring似乎使用一些ForkJoinPool
线程来执行我的代码,它会创建一个";异步线程";无论如何,但不要使用它。这更令人困惑-_-">
这里的问题是您正在使用CompletableFuture.supplyAsync
来生成CompletableFuture
。
此方法将派生一个在ForkJoinPool.commonPool((中运行的任务,并在该任务中执行您的Supplier。
由于spring将提供执行器并异步运行整个方法,因此不需要将supplyAsync
函数与lambda一起使用。相反,你的异步方法应该看起来像这样(在你的服务内部(:
@Service
public class MyService {
...
@Async("asyncExecutor")
public CompletableFuture<User> calculateRewards(User user) {
logger.info("Calculating Reward for user : " + user.getUserName());
List<VisitedLocation> userLocations = user.getVisitedLocations();
List<Attraction> attractions = gps.getAllAttraction();
for(VisitedLocation visitedLocation : userLocations) {
for(Attraction attraction : attractions) {
if(user.getUserRewards().stream().filter(r -> r.attraction.attractionName.equals(attraction.attractionName)).count() == 0) {
if(nearAttraction(visitedLocation, attraction)) {
user.addUserReward(new UserReward(visitedLocation, attraction, reward.getAttractionRewardPoints(attraction.attractionId, user.getUserId())));
}
}
}
}
return CompletableFuture.completedFuture(user);
}
}
如果您将服务自动连接到执行方法的CommandLineRunner
中,则可以使用此runner查看spring将使用您在配置中定义的Executor
异步执行方法。
例如:
@Component
public class Runner implements CommandLineRunner {
private final MyService service;
public Runner(MyService service) {
this.service = service;
}
@Override
public void run(String... args) throws Exception {
CompletableFuture<User> user1 = service.calculateRewards(new User("user1"));
CompletableFuture<User> user2 = service.calculateRewards(new User("user2"));
CompletableFuture<User> user3 = service.calculateRewards(new User("user3"));
CompletableFuture.allOf(user1,user2,user3).join();
}
}