Overview

スレッド初期化のシーケンス図

Explanation

SpringBootアプリケーション起動直後に、Tomcatのスレッドを初期化する方法について記載します。

処理フローは概要の「スレッド初期化のシーケンス図」の通りです。

以下、実際のプログラムを載せておきます。

1. ThreadInitializer

  • main threadで実行されるプログラムは、onApplicationEventメソッド。
  • child pool threadで実行されるプログラムは、callThreadInitメソッド。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Component
@Slf4j
public class ThreadInitializer implements ApplicationListener<ContextRefreshedEvent> {
    @Value("${server.tomcat.max-threads}")
    private Integer threadNum;

    @Autowired
    private RestTemplate restTemplate;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent arg0) {
        ExecutorService executor = Executors.newFixedThreadPool(threadNum);

        IntStream.range(0, threadNum).forEach(i -> {
            executor.submit(() -> {
                callThreadInit();
            });
        });

        executor.shutdown();
    }

    private void callThreadInit() {
        try {
            Thread.sleep(10000); // サーバーが立ち上がるまで10秒待機する。
            restTemplate.getForEntity("http://localhost:8080/threadInit", String.class);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2. Access point for thread initialization

tomcat threadで実行されるプログラムは、threadInitメソッド。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@RestController
@Slf4j
public class RestController {

    @RequestMapping("/threadInit")
    public void threadInit() throws InterruptedException {
        log.info("do something for thread init.");
        Thread.sleep(10000); // 10秒ほどかかる処理とする。
    }

}

3. Execution result

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
18:36:04: Executing task 'ThreadInitOnDeploymentApplication.main()'...

> Task :compileJava
> Task :processResources UP-TO-DATE
> Task :classes

> Task :ThreadInitOnDeploymentApplication.main()

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.6.RELEASE)

2020-04-25 18:36:08.943  INFO 95474 --- [           main] c.e.t.ThreadInitOnDeploymentApplication  : Starting ThreadInitOnDeploymentApplication on MacBook-Air.local with PID 95474 (/Users/takuto-n/Documents/Basic_study/private_repo/ctrl-key-6bit-mask/java/thread-init-on-deployment/build/classes/java/main started by takuto-n in /Users/takuto-n/Documents/Basic_study/private_repo/ctrl-key-6bit-mask/java/thread-init-on-deployment)
2020-04-25 18:36:08.949  INFO 95474 --- [           main] c.e.t.ThreadInitOnDeploymentApplication  : No active profile set, falling back to default profiles: default
2020-04-25 18:36:10.715  INFO 95474 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-04-25 18:36:10.755  INFO 95474 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-04-25 18:36:10.756  INFO 95474 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.33]
2020-04-25 18:36:10.896  INFO 95474 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-04-25 18:36:10.897  INFO 95474 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1842 ms
2020-04-25 18:36:11.243  INFO 95474 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-04-25 18:36:11.514  INFO 95474 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-04-25 18:36:11.527  INFO 95474 --- [           main] c.e.t.ThreadInitOnDeploymentApplication  : Started ThreadInitOnDeploymentApplication in 3.404 seconds (JVM running for 4.105)
2020-04-25 18:36:21.721  INFO 95474 --- [nio-8080-exec-7] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-04-25 18:36:21.721  INFO 95474 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-04-25 18:36:21.733  INFO 95474 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet        : Completed initialization in 12 ms
2020-04-25 18:36:21.780  INFO 95474 --- [nio-8080-exec-4] c.e.t.RestController                     : do something for thread init.
2020-04-25 18:36:21.780  INFO 95474 --- [nio-8080-exec-5] c.e.t.RestController                     : do something for thread init.
2020-04-25 18:36:21.780  INFO 95474 --- [nio-8080-exec-1] c.e.t.RestController                     : do something for thread init.
2020-04-25 18:36:21.781  INFO 95474 --- [nio-8080-exec-8] c.e.t.RestController                     : do something for thread init.
2020-04-25 18:36:21.781  INFO 95474 --- [nio-8080-exec-6] c.e.t.RestController                     : do something for thread init.
2020-04-25 18:36:21.781  INFO 95474 --- [nio-8080-exec-2] c.e.t.RestController                     : do something for thread init.
2020-04-25 18:36:21.780  INFO 95474 --- [nio-8080-exec-9] c.e.t.RestController                     : do something for thread init.
2020-04-25 18:36:21.782  INFO 95474 --- [io-8080-exec-10] c.e.t.RestController                     : do something for thread init.
2020-04-25 18:36:21.781  INFO 95474 --- [nio-8080-exec-3] c.e.t.RestController                     : do something for thread init.
2020-04-25 18:36:21.781  INFO 95474 --- [nio-8080-exec-7] c.e.t.RestController                     : do something for thread init.

4. Considered Point

4.1. 1st Challenge

TomcatThreadPool はどこで管理されているのかを確認して、そこでスレッド初期化を実施すればいいんじゃないか?と思い、Tomcat のソースコードを追った。

4.2. 2nd Challenge

色々試行錯誤し、「java how to get tomcat thread pool」のような単語で、ググってみた。

  • TomcatのJMXにWebアプリの中からアクセスしたい | DA BLOG」のブログを発見。
  • 以下のような何かそれっぽい単語が出てきたので、これでmain thread側から簡単にTomcatThreadPoolにアクセスできるのかな?と思った。
    • Tomcat
    • JMX(Java Management Extensions)
    • Webアプリからアクセス
  • そこで、以下のプログラムを組んでみた。
    • application.propertiesで以下の設定が必要。
      • server.tomcat.mbeanregistry.enabled=true
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        ObjectName objectName = 
            new ObjectName("Tomcat:type=ThreadPool,name=\"http-nio-8080\"");
        String[] attributes = new String[] { "currentThreadsBusy", "maxThreads" };
        AttributeList attrList = server.getAttributes(objectName, attributes);
    
        attrList.asList()
                .forEach(attr -> {
            String name = attr.getName();
            Object value = attr.getValue();
            log.info("name={}, value={}", name, value);
        });
    
  • 実行結果
    1
    2
    
    2020-04-25 18:25:53.904  INFO 94266 --- [           main] c.e.t.trash.OnStartServer                : className=javax.management.Attribute, name=currentThreadsBusy, value=-2
    2020-04-25 18:25:53.907  INFO 94266 --- [           main] c.e.t.trash.OnStartServer                : className=javax.management.Attribute, name=maxThreads, value=10
    
    • 結果、、、これではない。

    • Java Flight Recorderなど、他のツールから「アプリケーションの情報を取得するためのもの」だった。

    • なお、MBean名は、Java Fight Recorderで確認できる。