Recently, when using SpringBoot @Async annotation + Java8 Completable to implement asynchrony in the project, I accidentally stepped on the pit, so I wrote this article to summarize.
1. Configuration class
Step 1: Create a new configuration class and implement the AsyncConfigurer interface;
Step 2: Implement getAsyncExecutor
the method, which returns a thread pool object;
Step 3: Implement getAsyncUncaughtExceptionHandler
the method, which returns a SimpleAsyncUncaughtExceptionHandler object, which is used to simply record exceptions Information;
Step 3: Use the @EnableAsync annotation on the configuration class;
@Configuration
@EnableAsync
public class AsyncConfiguration implements AsyncConfigurer {
@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("order-Executor-");
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
2. Implement asynchronous methods
The first step: create a new component class, which can be any component managed by Spring, such as service, component, etc.; the
second step: provide an asynchronous method in the class, and use @Async annotation on the method;
@Service
public class DemoService {
@Async("taskExecutor")
public CompletableFuture<String> asyncTask1() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return CompletableFuture.completedFuture("test1");
}
@Async("taskExecutor")
public CompletableFuture<String> asyncTask2() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return CompletableFuture.completedFuture("test2");
}
@Async("taskExecutor")
public CompletableFuture<String> asyncTask3() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return CompletableFuture.completedFuture("test3");
}
}
Each of the above asynchronous methods takes 3 seconds to execute.
3. Call the asynchronous method
Here the asynchronous method defined above must be called on another class.
@SpringBootApplication
@Controller
public class AsynctestApplication {
@Autowired
DemoService demoService;
public static void main(String[] args) {
SpringApplication.run(AsynctestApplication.class, args);
}
@RequestMapping("/index")
@ResponseBody
public String index() {
long start = System.currentTimeMillis();
CompletableFuture<String> task1 = demoService.asyncTask1();
CompletableFuture<String> task2 = demoService.asyncTask2();
CompletableFuture<String> task3 = demoService.asyncTask3();
CompletableFuture[] collect = {
task1, task2, task3};
CompletableFuture<List<String>> res = CompletableFuture.allOf(collect).thenApply(ignoredVoid -> {
List list = Arrays.stream(collect).map(CompletableFuture::join).collect(Collectors.toList());
System.out.println(list);
return list;
});
try {
// 控制异步任务的执行时间
res.get(5, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("一共花费了" + (end - start) + "毫秒");
return "index";
}
}
Judging from the running results, because the three methods are executed asynchronously and simultaneously, the program ends after running for more than 3 seconds.
Finally, let me talk about the pit I stepped on, that is, at the beginning, the method that calls the asynchronous code is also implemented in the class where the asynchronous method is located, resulting in the failure of the execution of the asynchronous method. For example, the following query method:
@Component
public class DemoService {
@Async("taskExecutor")
public CompletableFuture<String> asyncTask1() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return CompletableFuture.completedFuture("test1");
}
@Async("taskExecutor")
public CompletableFuture<String> asyncTask2() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return CompletableFuture.completedFuture("test2");
}
@Async("taskExecutor")
public CompletableFuture<String> asyncTask3() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return CompletableFuture.completedFuture("test3");
}
public List<String> query() {
CompletableFuture<String> task1 = asyncTask1();
CompletableFuture<String> task2 = asyncTask2();
CompletableFuture<String> task3 = asyncTask3();
CompletableFuture[] collect = {
task1, task2, task3};
CompletableFuture<List<String>> res = CompletableFuture.allOf(collect).thenApply(ignoredVoid -> {
List list = Arrays.stream(collect).map(CompletableFuture::join).collect(Collectors.toList());
return list;
});
try {
return res.get(5, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
return null;
}
}
Because the query method is placed in the same class as other asynchronous methods, the asynchronous function of the program fails. At this time, even if the task times out, no java.util.concurrent.TimeoutException
exception will be thrown.