ããã¯ããªã«ãããããŠæžãããã®ïŒ
Spring Frameworkã«ã¯ãã¿ã¹ã¯å®è¡ãšã¹ã±ãžã¥ãŒãªã³ã°ã®æ©èœããããŸãã
ä»åã¯ãã¿ã¹ã¯å®è¡ã«ãã©ãŒã«ã¹ããŠèŠãŠãããããšæããŸãã
å
·äœçã«ã¯ãTaskExecutor
ãšThreadPoolTaskExecutor
ã@Async
ã¢ãããŒã·ã§ã³ãæ±ããŸãã
Task Execution
Spring Frameworkã®Task ExecutionâŠã¿ã¹ã¯ã®å®è¡ã«é¢ããããã¥ã¡ã³ãã¯ããã¡ãã§ãã
Spring Frameworkã§ã¯ãJavaã®Executor
ãæœè±¡åããTaskExecutor
ãæäŸããŠããŸãã
The Spring TaskExecutor Abstraction
Java SEç°å¢ã§ãã£ãŠãJava EEç°å¢ã§ãã£ãŠãããã¡ãã䜿ãããšã§ã¿ã¹ã¯å®è¡ã«é¢ããå®è£ ã®è©³çŽ°ãé èœãããŸãã
TaskExecutor
ã¯Executor
ãç¶æ¿ããã€ã³ã¿ãŒãã§ãŒã¹ã§ããããã®å®è£
ã¯Spring FrameworkãæäŸããŠããŸãã
Spring Frameworkã®å©çšè
èªèº«ããTaskExecutor
ã®å®è£
ãäœæããããšã¯ã»ãšãã©ãªãã§ãããã
TaskExecutor (Spring Framework 5.3.6 API)
TaskExecutor
ã®å®è£
ã«ã¯ã以äžã®çš®é¡ããããŸãã
SyncTaskExecutor
⊠ã¿ã¹ã¯ã®å®è¡ã¯å¥ã¹ã¬ããã§è¡ããã®ã®ãã¿ã¹ã¯ãéåæã§ã¯å®è¡ããªãå®è£ ããã¹ããªã©ããã«ãã¹ã¬ãããäžèŠãªç¶æ³äžã§äœ¿çšããSimpleAsyncTaskExecutor
⊠åŒã³åºãããšã«ãæ°ããã¹ã¬ãããäœæããŠå®è¡ããå®è£ ãåæã«å®è¡ã§ããã¿ã¹ã¯ã®æ°ã¯å¶éã§ããConcurrentTaskExecutor
âŠjava.util.concurrent.Executor
ã®ã¢ããã¿ãŒãéåžžã¯ãThreadPoolTaskExecutor
ã䜿çšããã°ããThreadPoolTaskExecutor
⊠æãäžè¬çãªå®è£ ãjava.util.concurrent.ThreadPoolExecutor
ãTaskExecutor
ãšããŠã©ããããŠãããä»ã®Executor
ã䜿ãããå Žåã¯ãConcurrentTaskExecutor
ã®å©çšãæ€èšããWorkManagerTaskExecutor
⊠CommonJ WorkManagerïŒWebSphereãWebLogicïŒãããã¯ãšã³ãã«äœ¿çšããå®è£DefaultManagedTaskExecutor
⊠Java EEç°å¢ã«ãããManagedExecutorService
ãããã¯ãšã³ãã«äœ¿çšããå®è£
ãããã«ãããTaskExecutor
ã¯Javaã®Executor
ã€ã³ã¿ãŒãã§ãŒã¹ã®æ¡åŒµãªã®ã§ã䜿ãæ¹ãåãã«ãªããŸãã
Executor (Java SE 11 & JDK 11 )
ãã䜿ãããããªThreadPoolTaskExecutor
ã¯ThreadPoolExecutor
ãã©ããããŠããã®ã§ããã¡ããåç
§ãããš
ããã§ãããã
ThreadPoolExecutor (Java SE 11 & JDK 11 )
ThreadPoolTaskExecutor (Spring Framework 5.3.6 API)
Spring Bootã䜿ã£ãå Žåã¯ãThreadPoolTaskExecutor
ãèªåæ§æãããŸãã
TaskExecutor
ã®äœ¿çšäŸã¯ãã¡ãã
ãŸãã@Async
ã¯ã¹ã±ãžã¥ãŒã«å®è¡ã«é¢ããæ©èœãªã®ã§ãããä»å䜿ã£ãŠã¿ãŸãã
@EnableAsync
ã䜿ã£ãŠæå¹åããããã§ãåŠçãè¡ãBeanã®ã¡ãœããã«@Async
ã¢ãããŒã·ã§ã³ãä»äžãããšã
æå®ãããïŒãŸãã¯ããã©ã«ãã®ïŒTaskExecutor
ã䜿ã£ãŠéåæå®è¡ãè¡ãããŸãã
ä»åã¯ãTaskExecutor
ãšThreadPoolTaskExecutor
ã@Async
ã¢ãããŒã·ã§ã³ã䜿ã£ãŠãããŸãã
ç°å¢
ä»åã®ç°å¢ã¯ããã¡ãã§ãã
$ java --version openjdk 11.0.10 2021-01-19 OpenJDK Runtime Environment (build 11.0.10+9-Ubuntu-0ubuntu1.20.04) OpenJDK 64-Bit Server VM (build 11.0.10+9-Ubuntu-0ubuntu1.20.04, mixed mode, sharing) $ mvn --version Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 11.0.10, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-72-generic", arch: "amd64", family: "unix"
Spring Bootã¯2.4.5ã䜿ããäŸåããSpring Frameworkã¯5.3.6ãšãªããŸãã
Spring Bootãããžã§ã¯ãã®äœæ
Spring Initializrã䜿ã£ãŠãSpring Bootãããžã§ã¯ããäœæããŸãã
ä»åã®ã¢ããªã±ãŒã·ã§ã³ã¯Webã§ã¯ãªããCommandLineRunner
ã§ç¢ºèªãããããã§è¯ãããªããšæã£ãã®ã§äŸåé¢ä¿ã¯
ç¹ã«æå®ããŠããŸããã
$ curl -s https://start.spring.io/starter.tgz \ -d bootVersion=2.4.5 \ -d javaVersion=11 \ -d name=task-execution \ -d groupId=org.littlewings \ -d artifactId=task-execution \ -d version=0.0.1-SNAPSHOT \ -d packageName=org.littlewings.spring.task \ -d baseDir=task-execution | tar zxvf - $ cd task-execution
dependencies
ããªã«ãæå®ããªãå Žåã¯ãäŸåé¢ä¿ããããããã®pom.xml
ã«ãªããŸãã
<properties> <java.version>11</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
èµ·åã¯ã©ã¹ã¯ããã¡ãã§ã
src/main/java/org/littlewings/spring/task/App.java
package org.littlewings.spring.task; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication //@EnableAsync public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
ã³ã¡ã³ãã¢ãŠãããŠãã@EnableAsync
ã¢ãããŒã·ã§ã³ã¯ãåŸè¿°ããŸãã
å®è¡ã¯ã以äžã®ã³ãã³ãã§è¡ã£ãŠãããã®ãšããŸãã
$ mvn spring-boot:run
ããšãCommandLineRunner
ã€ã³ã¿ãŒãã§ãŒã¹ã®å®è£
ã¯ã©ã¹ã¯è©Šãããæ©èœããšã«äœã£ãŠããã®ã§ããããã®ãŸãŸå¢ãããš
å
šéšå®è¡ããŠãã£ãŠããŸãã®ã§ã玹ä»ãçµãã£ãã以äžã®ããã«@Component
ã¢ãããŒã·ã§ã³ãã³ã¡ã³ãã¢ãŠãããŠãã
ãã®ãšããŸãã
// @Component public class SimpleRunner implements CommandLineRunner {
ãŸãã¯äœ¿ã£ãŠã¿ã
ã§ã¯ã䜿ã£ãŠãã£ãŠã¿ãŸãã
ãã¡ããåèã«
ãããªã¯ã©ã¹ãäœæã
src/main/java/org/littlewings/spring/task/SimpleRunner.java
package org.littlewings.spring.task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.CommandLineRunner; import org.springframework.core.task.TaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; @Component public class SimpleRunner implements CommandLineRunner { Logger logger = LoggerFactory.getLogger(SimpleRunner.class); TaskExecutor taskExecutor; public SimpleRunner(TaskExecutor taskExecutor) { this.taskExecutor = taskExecutor; } @Override public void run(String... args) throws Exception { taskExecutor.execute(() -> { logger.info("Hello World!!"); logger.info("TaskExecutor name = {}", taskExecutor.getClass().getName()); }); taskExecutor.execute(() -> logger.info("Oops!!")); ThreadPoolTaskExecutor threadPoolTaskExecutor = (ThreadPoolTaskExecutor) taskExecutor; logger.info("core pool size = {}, max pool size = {}, keep-alive seconds = {}", threadPoolTaskExecutor.getCorePoolSize(), threadPoolTaskExecutor.getMaxPoolSize(), threadPoolTaskExecutor.getKeepAliveSeconds()); } }
DIããåã¯ãTaskExecutor
ã€ã³ã¿ãŒãã§ãŒã¹ã§ã倧äžå€«ã§ãã
TaskExecutor taskExecutor; public SimpleRunner(TaskExecutor taskExecutor) { this.taskExecutor = taskExecutor; }
TaskExecutor#execute
ã¡ãœããã«ãRunnable
ãæž¡ãããšã§ã¿ã¹ã¯ãå®è¡ã§ããŸããä»åã¯ããã°åºåãããŠããã ãã§ãã
TaskExecutor
ã®å®è£
ã¯ã©ã¹åãåºåããŠããŸãã
taskExecutor.execute(() -> { logger.info("Hello World!!"); logger.info("TaskExecutor name = {}", taskExecutor.getClass().getName()); }); taskExecutor.execute(() -> logger.info("Oops!!"));
TaskExecutor
ã€ã³ã¿ãŒãã§ãŒã¹ãã§ããã®ã¯ãããã ãã§ãã
TaskExecutor (Spring Framework 5.3.6 API)
ã¿ã¹ã¯ããã®æ»ãå€ãååŸã§ããŸããã
å®äœã¯ThreadPoolTaskExecutor
ãªã®ã§ããã£ã¹ãããŠèšå®ãèŠãŠã¿ãŸãããã
ThreadPoolTaskExecutor threadPoolTaskExecutor = (ThreadPoolTaskExecutor) taskExecutor;
logger.info("core pool size = {}, max pool size = {}, keep-alive seconds = {}", threadPoolTaskExecutor.getCorePoolSize(), threadPoolTaskExecutor.getMaxPoolSize(), threadPoolTaskExecutor.getKeepAliveSeconds());
å®è¡çµæã
2021-04-25 22:36:55.048 INFO 43283 --- [ task-1] o.littlewings.spring.task.SimpleRunner : Hello World!! 2021-04-25 22:36:55.049 INFO 43283 --- [ task-2] o.littlewings.spring.task.SimpleRunner : Oops!! 2021-04-25 22:36:55.049 INFO 43283 --- [ task-1] o.littlewings.spring.task.SimpleRunner : TaskExecutor name = org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor 2021-04-25 22:36:55.048 INFO 43283 --- [ main] o.littlewings.spring.task.SimpleRunner : core pool size = 8, max pool size = 2147483647, keep-alive seconds = 60 2021-04-25 22:37:55.052 INFO 43283 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
ãã°ã®ã¹ã¬ããåãèŠããšãtask-
ãšãªã£ãŠããŠãå¥ã¹ã¬ããã§å®è¡ãããŠããããšãããããŸãã
䜿çšãããŠããThreadPoolTaskExecutor
ãAutoConfigureãããŠããã®ã¯ããã¡ãã§ããã
ããããã£èšå®ã¯ããã¡ããã
ããã©ã«ãã§ã¯8ã¹ã¬ãããã³ã¢ãµã€ãºãšããŠæã¡ãæ倧ã¹ã¬ããæ°ã¯Integer.MAX_VALUE
ãŸã§åºããã¹ã¬ããããŒã«ã
æ§æãããŸãã
ãšããã§ãã®ããã°ã©ã ãå®è¡ãããšãã°ããçµäºããŸããã
åæ¢ãŸã§1åããã£ãŠããŸãã
2021-04-25 22:36:55.048 INFO 43283 --- [ main] o.littlewings.spring.task.SimpleRunner : core pool size = 8, max pool size = 2147483647, keep-alive seconds = 60 2021-04-25 22:37:55.052 INFO 43283 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
spring.task.execution.pool.allow-core-thread-timeout
ãããã©ã«ãã§true
ã«ãªã£ãŠããŠã¿ã¹ã¯ãå®è¡ããã¹ã¬ããã¯ã¿ã€ã ã¢ãŠã
ããèšå®ã«ãªã£ãŠããã®ã§ãããspring.task.execution.pool.keep-alive
ãããã©ã«ãã§60ç§ãªã®ã§ããã®éã¯åŸ
ã¡ç¶ããŸãã
ã¡ãã£ãšé·ãã®ã§ãä»åã¯3ç§ã«ããŠãããŸãããã
src/main/resources/application.properties
spring.task.execution.pool.keep-alive=3s
ä»åºŠã¯ã3ç§ã§çµäºããããã«ãªããŸããã
2021-04-25 22:44:36.363 INFO 43800 --- [ task-1] o.littlewings.spring.task.SimpleRunner : Hello World!! 2021-04-25 22:44:36.364 INFO 43800 --- [ task-2] o.littlewings.spring.task.SimpleRunner : Oops!! 2021-04-25 22:44:36.364 INFO 43800 --- [ task-1] o.littlewings.spring.task.SimpleRunner : TaskExecutor name = org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor 2021-04-25 22:44:36.364 INFO 43800 --- [ main] o.littlewings.spring.task.SimpleRunner : core pool size = 8, max pool size = 2147483647, keep-alive seconds = 3 2021-04-25 22:44:39.369 INFO 43800 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
ThreadPoolTaskExecutorã䜿ã
å
ã»ã©ã¯TaskExecutor
ã€ã³ã¿ãŒãã§ãŒã¹ã®ãŸãŸã¿ã¹ã¯ãå®è¡ããŸããããTaskExecutor#execute
ã ãšRunnable
ãã
å®è¡ã§ããªãã®ã§ã¡ãã£ãšäžäŸ¿ã§ãâŠã
ãããThreadPoolTaskExecutor
ã§DIããŠããã®ã§ã¯ãªãã®ã§ããããïŒãšããæ°åã«ãªããŸãã
ãšããããã§ããã®ããã«æžããŠã¿ãŸããã
src/main/java/org/littlewings/spring/task/ThreadPoolTaskExecutorRunner.java
package org.littlewings.spring.task; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.CommandLineRunner; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; @Component public class ThreadPoolTaskExecutorRunner implements CommandLineRunner { Logger logger = LoggerFactory.getLogger(ThreadPoolTaskExecutorRunner.class); ThreadPoolTaskExecutor taskExecutor; public ThreadPoolTaskExecutorRunner(ThreadPoolTaskExecutor taskExecutor) { this.taskExecutor = taskExecutor; } @Override public void run(String... args) throws Exception { List<Future<?>> futures = new ArrayList<>(); futures.add(taskExecutor.submit(() -> { logger.info("calc task start"); try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { // ignore } return 1 + 2; })); futures.add(taskExecutor.submit(() -> { logger.info("message task start"); try { TimeUnit.SECONDS.sleep(5L); } catch (InterruptedException e) { // ignore } return "Hello World!!"; })); for (Future<?> future : futures) { logger.info("future get = {}", future.get()); } } }
ãã£ãŠããããšã¯ãCallable
ã䜿ã£ãŠçµæãè¿ãããã«ããŠFuture
ãåãåãã
futures.add(taskExecutor.submit(() -> { logger.info("calc task start"); try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { // ignore } return 1 + 2; })); futures.add(taskExecutor.submit(() -> { logger.info("message task start"); try { TimeUnit.SECONDS.sleep(5L); } catch (InterruptedException e) { // ignore } return "Hello World!!"; }));
æåŸã«åŸ ã¡åãããŠããã ãã§ãã
for (Future<?> future : futures) { logger.info("future get = {}", future.get()); }
å®è¡çµæã
2021-04-25 22:53:40.708 INFO 44289 --- [ task-1] o.l.s.task.ThreadPoolTaskExecutorRunner : calc task start 2021-04-25 22:53:40.709 INFO 44289 --- [ task-2] o.l.s.task.ThreadPoolTaskExecutorRunner : message task start 2021-04-25 22:53:43.710 INFO 44289 --- [ main] o.l.s.task.ThreadPoolTaskExecutorRunner : future get = 3 2021-04-25 22:53:45.710 INFO 44289 --- [ main] o.l.s.task.ThreadPoolTaskExecutorRunner : future get = Hello World!!
ãã°ãèŠãŠããå¥ã¹ã¬ããã§å®è¡ãããŠããããšã確èªã§ããŸãããçµæãååŸã§ããŠããŸãã
@Asyncã䜿ã£ãŠéåæå®è¡ãã
次ã¯ã@Async
ã䜿ã£ãŠéåæå®è¡ããŠã¿ãŸãããã@Async
ã¢ãããŒã·ã§ã³ã¯ãã¹ã±ãžã¥ãŒã«å®è¡åŽã®æ©èœã§ãã
æåã«ãããŸããããªãäŸãæžããŠã¿ãŸãã
src/main/java/org/littlewings/spring/task/MessageService.java
package org.littlewings.spring.task; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Service; @Service public class MessageService { Logger logger = LoggerFactory.getLogger(MessageService.class); @Async public String getMessage() { logger.info("get message start"); Thread.dumpStack(); try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { // ignore } return "Hello World"; } }
ãããªæãã§ãéåæå®è¡ãããã¡ãœããã«@Async
ã¢ãããŒã·ã§ã³ãä»äžããŸããã¹ã¿ãã¯ãã¬ãŒã¹ã®æ§åãèŠããã®ã§ã
Thread#dumpStack
ãå«ããŠãããŸããã
@Async public String getMessage() { logger.info("get message start"); Thread.dumpStack(); try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { // ignore } return "Hello World"; }
ãã¡ãã䜿ãã¯ã©ã¹ãäœæããã®ãã¿ãŒã³ã§ã¯ãTaskExecutor
ãThreadPoolTaskExecutor
ã¯ãæåŸãŸã§ãœãŒã¹ã³ãŒãã«çŸããŸããã
src/main/java/org/littlewings/spring/task/UseAsyncRunner.java
package org.littlewings.spring.task; import java.util.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class UseAsyncRunner implements CommandLineRunner { Logger logger = LoggerFactory.getLogger(UseAsyncRunner.class); MessageService messageService; public UseAsyncRunner(MessageService messageService) { this.messageService = messageService; } @Override public void run(String... args) throws Exception { String message = messageService.getMessage(); logger.info("return message = {}", message); } }
å®è¡ããŠãã°ãããèŠããšãåã«åãã¹ã¬ããã§åããŠããã ãã§ãã
2021-04-25 23:06:59.317 INFO 45078 --- [ main] o.l.spring.task.MessageService : get message start java.lang.Exception: Stack trace at java.base/java.lang.Thread.dumpStack(Thread.java:1388) at org.littlewings.spring.task.MessageService.getMessage(MessageService.java:22) at org.littlewings.spring.task.UseAsyncRunner.run(UseAsyncRunner.java:22) at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:819) at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:803) at org.springframework.boot.SpringApplication.run(SpringApplication.java:346) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1340) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1329) at org.littlewings.spring.task.App.main(App.java:11) 2021-04-25 23:07:02.318 INFO 45078 --- [ main] o.l.spring.task.UseAsyncRunner : return message = Hello World
ãã®æ©èœã䜿ãã«ã¯ã@EnableAsync
ã¢ãããŒã·ã§ã³ã䜿ãå¿
èŠãããããã§ãã
ããã§ã@Async
ã¢ãããŒã·ã§ã³ãæ©èœããããã«ãªããŸãã
@SpringBootApplication @EnableAsync public class App {
å床å®è¡ã
2021-04-25 23:08:43.523 INFO 45212 --- [ main] o.l.spring.task.UseAsyncRunner : return message = null 2021-04-25 23:08:43.534 INFO 45212 --- [ task-1] o.l.spring.task.MessageService : get message start java.lang.Exception: Stack trace at java.base/java.lang.Thread.dumpStack(Thread.java:1388) at org.littlewings.spring.task.MessageService.getMessage(MessageService.java:22) at org.littlewings.spring.task.MessageService$$FastClassBySpringCGLIB$$ace3701c.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at java.base/java.lang.Thread.run(Thread.java:834)
ä»åºŠã¯ãå¥ã¹ã¬ããã§åãããã«ãªããŸãããã¹ã¿ãã¯ãã¬ãŒã¹ãèŠããšãã€ã³ã¿ãŒã»ãã¿ãŒãå ¥ã£ãŠããŸããã
ã§ãããã¡ãœããã®æ»ãå€ãååŸã§ããŠããŸããã
2021-04-25 23:08:43.523 INFO 45212 --- [ main] o.l.spring.task.UseAsyncRunner : return message = null
ããã¥ã¡ã³ããã¡ãããšèªããšãå€ãè¿ãå Žåã¯Future
ã䜿ãå¿
èŠãããããã§ãã
Even methods that return a value can be invoked asynchronously. However, such methods are required to have a Future-typed return value. This still provides the benefit of asynchronous execution so that the caller can perform other tasks prior to calling get() on that Future.
ããã¥ã¡ã³ããèŠãŠãããšæ»ãå€ãvoid
ã®äŸã°ããã§ãããvoid
ã®å Žåã ãšçµæãåŸ
ããªãïŒåŸ
ãŠãªãïŒéåæå®è¡ã«ãªããŸãã
ã¡ãœããã®æ»ãå€ãäžèŠã§ããã°ãvoid
ã«ããŠå®è¡ããŠãããã§ãããã
ä»åã¯æ»ãå€ã䜿ãããã®ã§ãã¡ãœããã®æ»ãå€ãFuture
ãšãªãããã«å€æŽããŠã¿ãŸãã
src/main/java/org/littlewings/spring/task/MessageService.java
package org.littlewings.spring.task; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Service; @Service public class MessageService { Logger logger = LoggerFactory.getLogger(MessageService.class); @Async public Future<String> getMessage() { logger.info("get message start"); Thread.dumpStack(); try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { // ignore } // return "Hello World"; return new AsyncResult<>("Hello World"); } }
ããã¥ã¡ã³ããvoid
ã®äŸã°ããã ã£ãã®ã§ãFuture
ãè¿ãã«ã¯ã©ããããâŠïŒãšããæ°åã«ãªããŸããããAsyncResult
ã
䜿ãã®ãè¯ãããã§ãã
AsyncResult (Spring Framework 5.3.6 API)
åŒã³åºãå ãå€æŽã
src/main/java/org/littlewings/spring/task/UseAsyncRunner.java
package org.littlewings.spring.task; import java.util.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; @Component public class UseAsyncRunner implements CommandLineRunner { Logger logger = LoggerFactory.getLogger(UseAsyncRunner.class); MessageService messageService; public UseAsyncRunner(MessageService messageService) { this.messageService = messageService; } @Override public void run(String... args) throws Exception { Future<String> message = messageService.getMessage(); logger.info("return message = {}", message.get()); } }
ã§ã¯ãå®è¡ã
2021-04-25 23:43:14.929 INFO 47028 --- [ task-1] o.l.spring.task.MessageService : get message start java.lang.Exception: Stack trace at java.base/java.lang.Thread.dumpStack(Thread.java:1388) at org.littlewings.spring.task.MessageService.getMessage(MessageService.java:37) at org.littlewings.spring.task.MessageService$$FastClassBySpringCGLIB$$ace3701c.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115) at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at java.base/java.lang.Thread.run(Thread.java:834) 2021-04-25 23:43:17.931 INFO 47028 --- [ main] o.l.spring.task.UseAsyncRunner : return message = Hello World
OKã§ããã
ã¡ãªã¿ã«ãFuture
以å€ã«ãSpringãæäŸããListenableFuture
ããããŠJava 8以éã®CompletableFuture
ãæ»ãå€ãšããŠ
䜿ããŸãã
ããã¥ã¡ã³ãã«èšèŒããããŸããã
@Async methods may not only declare a regular java.util.concurrent.Future return type but also Springâs org.springframework.util.concurrent.ListenableFuture or, as of Spring 4.2, JDK 8âs java.util.concurrent.CompletableFuture, for richer interaction with the asynchronous task and for immediate composition with further processing steps.
ãã®ããããèŠãã°ããããŸãã
åŒã³åºãå
ã¯ããã¡ããã¡ãœãããCallable
ã§ã©ããããŠå®è¡ããŸãã
ãã®æ䜿ãããTaskExecutor
ã¯ããããŸã§äœ¿ã£ãŠããSpring Bootã§ããã©ã«ãæ§æãããThreadPoolTaskExecutor
ã§ãã
ãŸããä»åã¯æ±ããŸããããäŸå€åŠçã«ã€ããŠã¯ãã¡ãã®ããã§ãã
Exception Management with @Async
èªåã§ThreadPoolTaskExecutorã®Beanãäœæãã
æåŸã«ãèªåã§ThreadPoolTaskExecutor
ã®BeanãäœæããŠã¿ãŸãããã
ãããªæãã§ã2ã€ã®ThreadPoolTaskExecutor
ã®Beanãç»é²ã
src/main/java/org/littlewings/spring/task/MyTaskExecutorConfiguration.java
package org.littlewings.spring.task; import java.time.Duration; import org.springframework.boot.task.TaskExecutorBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @Configuration public class MyTaskExecutorConfiguration { @Bean("largePoolTaskExecutor") public ThreadPoolTaskExecutor largePoolTaskExecutor() { TaskExecutorBuilder builder = new TaskExecutorBuilder(); return builder .corePoolSize(16) .maxPoolSize(16) .keepAlive(Duration.ofSeconds(2L)) .queueCapacity(Integer.MAX_VALUE) .threadNamePrefix("large-pool-task-") .allowCoreThreadTimeOut(true) .build(); } @Bean("smallPoolTaskExecutor") public ThreadPoolTaskExecutor smallPoolTaskExecutor() { TaskExecutorBuilder builder = new TaskExecutorBuilder(); return builder .corePoolSize(2) .maxPoolSize(2) .keepAlive(Duration.ofSeconds(2L)) .queueCapacity(Integer.MAX_VALUE) .threadNamePrefix("small-pool-task-") .allowCoreThreadTimeOut(true) .build(); } }
ThreadPoolTaskExecutor
ã®äœæã«ã¯ãTaskExecutorBuilder
ã䜿ãã®ãè¯ãã§ãããã
The auto-configured TaskExecutorBuilder allows you to easily create instances that reproduce what the auto-configuration does by default.
Both a TaskExecutorBuilder bean and a TaskSchedulerBuilder bean are made available in the context if a custom executor or scheduler needs to be created.
TaskExecutorBuilder (Spring Boot 2.4.5 API)
䜿ãæ¹ã¯ãSpring Bootèªèº«ãåèã«ãªããŸãã
ä»åã¯ããµã€ãºåºå®ã®ã¹ã¬ããããŒã«ã2ã€çšæããŸããã
@Bean("largePoolTaskExecutor") public ThreadPoolTaskExecutor largePoolTaskExecutor() { TaskExecutorBuilder builder = new TaskExecutorBuilder(); return builder .corePoolSize(16) .maxPoolSize(16) .keepAlive(Duration.ofSeconds(2L)) .queueCapacity(Integer.MAX_VALUE) .threadNamePrefix("large-pool-task-") .allowCoreThreadTimeOut(true) .build(); } @Bean("smallPoolTaskExecutor") public ThreadPoolTaskExecutor smallPoolTaskExecutor() { TaskExecutorBuilder builder = new TaskExecutorBuilder(); return builder .corePoolSize(2) .maxPoolSize(2) .keepAlive(Duration.ofSeconds(2L)) .queueCapacity(Integer.MAX_VALUE) .threadNamePrefix("small-pool-task-") .allowCoreThreadTimeOut(true) .build(); }
å®çŸ©ããThreadPoolTaskExecutor
ã䜿ãåŽã®ã¯ã©ã¹ã
src/main/java/org/littlewings/spring/task/CustomTaskExecutorRunner.java
package org.littlewings.spring.task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.CommandLineRunner; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; @Component public class CustomTaskExecutorRunner implements CommandLineRunner { Logger logger = LoggerFactory.getLogger(CustomTaskExecutorRunner.class); ThreadPoolTaskExecutor largePoolTaskExecutor; ThreadPoolTaskExecutor smallPoolTaskExecutor; public CustomTaskExecutorRunner(@Qualifier("largePoolTaskExecutor") ThreadPoolTaskExecutor taskExecutor1, @Qualifier("smallPoolTaskExecutor") ThreadPoolTaskExecutor taskExecutor2) { this.largePoolTaskExecutor = taskExecutor1; this.smallPoolTaskExecutor = taskExecutor2; } @Override public void run(String... args) throws Exception { largePoolTaskExecutor.execute(() -> logger.info("hello")); smallPoolTaskExecutor.execute(() -> logger.info("world")); logger.info("pool prefix = {}, core pool size = {}, max pool size = {}, keep-alive seconds = {}", largePoolTaskExecutor.getThreadNamePrefix(), largePoolTaskExecutor.getCorePoolSize(), largePoolTaskExecutor.getMaxPoolSize(), largePoolTaskExecutor.getKeepAliveSeconds()); logger.info("pool prefix = {}, core pool size = {}, max pool size = {}, keep-alive seconds = {}", smallPoolTaskExecutor.getThreadNamePrefix(), smallPoolTaskExecutor.getCorePoolSize(), smallPoolTaskExecutor.getMaxPoolSize(), smallPoolTaskExecutor.getKeepAliveSeconds()); } }
ThreadPoolTaskExecutor
ã®åºå¥ã¯ã@Qualifier
ã¢ãããŒã·ã§ã³ã§è¡ãããšã«ããŸããã
public CustomTaskExecutorRunner(@Qualifier("largePoolTaskExecutor") ThreadPoolTaskExecutor taskExecutor1, @Qualifier("smallPoolTaskExecutor") ThreadPoolTaskExecutor taskExecutor2) { this.largePoolTaskExecutor = taskExecutor1; this.smallPoolTaskExecutor = taskExecutor2; }
ä»åã®ThreadPoolTaskExecutor
ã®äœ¿ãéã¯ãåã«ã¡ãã»ãŒãžã®ãã°åºåãšãèšå®å€ã®ç¢ºèªã®ããã®ãã°åºåãããã§ããã©ãã
å®è¡çµæã
2021-04-26 00:07:16.757 INFO 48670 --- [rge-pool-task-1] o.l.s.task.CustomTaskExecutorRunner : hello 2021-04-26 00:07:16.758 INFO 48670 --- [all-pool-task-1] o.l.s.task.CustomTaskExecutorRunner : world 2021-04-26 00:07:16.758 INFO 48670 --- [ main] o.l.s.task.CustomTaskExecutorRunner : pool prefix = large-pool-task-, core pool size = 16, max pool size = 16, keep-alive seconds = 2 2021-04-26 00:07:16.758 INFO 48670 --- [ main] o.l.s.task.CustomTaskExecutorRunner : pool prefix = small-pool-task-, core pool size = 2, max pool size = 2, keep-alive seconds = 2
èšå®ããå 容ãåæ ãããŠããããã§ãã
OKã§ããã
ãã®ããã«èªåã§å®çŸ©ããTaskExecutor
ã@Async
ã¢ãããŒã·ã§ã³ã§æå®ããã«ã¯ã@Async
ã¢ãããŒã·ã§ã³ã®value
ã«
æå®ããã°ããã¿ããã§ãã
Executor Qualification with @Async
ä»åã¯ãæ±ããŸãããã
ãŸãšã
Spring Frameworkã®Task ExecutionããTaskExecutor
ãšThreadPoolTaskExecutor
ã@Async
ã¢ãããŒã·ã§ã³ã«çµã£ãŠ
䜿ã£ãŠã¿ãŸããã
Javaã®ExecutorService
ãšããå€ãããªãã®ã ãããšæã£ãŠããã®ã§ããããããæ£è§£ã§ããããåºæã®ããšããã£ãæãã§ããã
å匷ã«ãªããŸããããšã