@AsyncとThreadPoolTaskExecutorを利用して、非同期処理を実装してみる

今日も備忘録です。

## このドキュメントについて
下記3点について記載していきます。

1. @Asyncを利用して、非同期処理を行う方法
2. ThreadPoolTaskExecutorの利用方法
4. @Asyncを利用する際の注意点

## 実行環境

・Java17
・springboot 3.0.2

## 実装
### @Asyncを利用する前の準備
``` java
package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@EnableAsync
public class ThreadPoolConfig {

    @Bean(name = "Thread1")
    ThreadPoolTaskExecutor configThreadPool() {
        var threadPool = new ThreadPoolTaskExecutor();
        threadPool.setCorePoolSize(2);
        threadPool.setMaxPoolSize(5);
        threadPool.setKeepAliveSeconds(0);
        threadPool.setQueueCapacity(500);
        threadPool.setDaemon(true);
        threadPool.setThreadNamePrefix("Thread1-");
        threadPool.initialize();
        return threadPool;
    }

    @Bean(name = "Thread2")
    ThreadPoolTaskExecutor configThreadPool2() {
        var threadPool = new ThreadPoolTaskExecutor();
        threadPool.setCorePoolSize(2);
        threadPool.setMaxPoolSize(5);
        threadPool.setKeepAliveSeconds(0);
        threadPool.setQueueCapacity(500);
        threadPool.setDaemon(true);
        threadPool.setThreadNamePrefix("Thread2-");
        threadPool.initialize();
        return threadPool;
    }
}
```
説明：
1. @Configurationと@Beanを利用して、DIコンテナにクラスを登録する
1. @Asyncが利用できるように@EnableAsyncを設定しておく
起動クラスか@Configurationが付与されたクラスにつけなければいけない。

ThreadPoolTaskExecutorの説明：
学習のため、いろいろ設定してみました。
1. setCorePoolSize()
・常時待機しているスレッドの数
1. setMaxPoolSize()
・QueueCapacityの数を超えた時に追加されるスレッドのMax値
1. setQueueCapacity()
・キューに貯めておけるタスク数
1. setKeepAliveSeconds()
・スレッドが待機している時間、この時間を過ぎると削除される、デフォルトは60秒
1. setDaemon()
・Javaアプリケーションが終了した時に、自動的に終了するスレッドに設定
1. setThreadNamePrefix()
スレッドの名前を設定できる
1. initialize()
これがないとThreadPoolTaskExecutorは生成されないので、注意

### @Asyncを利用して非同期処理

簡単にですが、Controllerとserviceクラスで実装してみました。

・Controller

``` java
@RestController
@RequiredArgsConstructor
public class AsyncController {
    private final AsyncService asyncService;

    @PostMapping("/useThreadPoolTaskAsync")
    public ResponseEntity<String> useThreadPoolTaskAsync() {
        asyncService.useThreadPoolTaskAsync();
        asyncService.useThreadPoolTaskAsync();
        asyncService.useThreadPoolTaskAsync();
        return ResponseEntity.ok("処理終了");
    }
}

```


・Service
``` java
@Service
@Slf4j
public class AsyncService {

    @Async("Thread1")
    public void useThreadPoolTaskAsync() {
        log.info("非同期start→" + Thread.currentThread().getId() + "、" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException("割り込み", e);
        }
        log.info("非同期finish→" + Thread.currentThread().getId() + "、"+ Thread.currentThread().getName());
    }
}

```
説明：
1. 呼び出されるメソッドに@Async("Thread1")を付与することで、@Bean(name="Thread1")と紐づき、設定したThreadPoolTaskExecutorが利用できる
1. スレッドのIDと名前が表示されるようになっていて、2つのスレッドがthread1という名前で動くのが分かる
1. 3回目実行分はキューに入れられるので、処理としては遅れて始まる

<font color="Red">注意点：</font>
同クラスの別メソッドから@Asyncが付与されたメソッドを非同期で実行しようとしても、実行されないので、注意が必要です。
具体的には以下。


``` java
@Service
@Slf4j
public class AsyncService {

    public void Test() {
         useThreadPoolTaskAsync();
         useThreadPoolTaskAsync();
         useThreadPoolTaskAsync();
    }

    @Async("Thread1")
    public void useThreadPoolTaskAsync() {
        log.info("非同期start→" + Thread.currentThread().getId() + "、" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException("割り込み", e);
        }
        log.info("非同期finish→" + Thread.currentThread().getId() + "、"+ Thread.currentThread().getName());
    }
}

```

これでは、useThreadPoolTaskAsyncメソッドは、非同期で実行されない。。
理由は分からないが、どうも別クラスから直接呼び出される or Serviceクラスをインスタンス化してuseThreadPoolTaskAsyncメソッドを呼び出すと非同期処理が走ります。

理由が分かる方がいらっしゃれば、教えていただきたいです。。

## まとめ
springの機能である@Asyncを利用して非同期処理を行う方法を書きました。
フィールドにThreadPoolTaskExecutorを宣言して、依存性注入をするやり方とどっちがいいのか、、
run()をオーバーライドしたりしたりする必要がなく、メソッド全体が非同期処理として動いてくれるのは楽だなと思いました。