Spring Bootでparallelや@EnableAsyncで非同期処理や並列処理を実装する

Spring Bootでparallelや@EnableAsyncで非同期処理や並列処理を実装する

Stream APIで並列処理を実装する

Java8以上の場合、stream apiでparallelメソッドで並列実行することが出来ます。

parallelメソッドは並列処理ですので非同期処理とは異なります。

以下ソースは、指定秒数かかるAPIを2回実行していますが、並列実行しているので指定秒数強で実行ができます。

/**
 * 指定秒数かかるAPI
 *
 * @param seconds
 * @return
 * @throws InterruptedException
 */
@RequestMapping(path = "/sleep/{seconds}", method = RequestMethod.GET)
public ResponseEntity<String> sleep(@PathVariable String seconds) throws InterruptedException {
  Thread.sleep(Long.valueOf(seconds) * 1000L); // スリープするだけ
  return ResponseEntity.ok("test");
}

/**
 * parallelで並列実行
 *
 * @param seconds
 * @return
 */
@RequestMapping(path = "/test/{seconds}", method = RequestMethod.GET)
public ResponseEntity<String> test(@PathVariable String seconds) {
  long startTime = System.currentTimeMillis();
  RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
  RestTemplate restTemplate = restTemplateBuilder.build();
  String url = String.format("http://localhost:8080/sleep/%s", seconds);
  List<String> list = List.of(url, url); // 並列
  String result =
    list.stream()
    .parallel()
    .map(
      e –> {
      return restTemplate.getForObject(url, String.class);
    })
    .collect(Collectors.joining(",", "{", "}"));
  long endTime = System.currentTimeMillis();
  System.out.println("処理時間:" + (endTime – startTime) + " ms");
  return ResponseEntity.ok(result);
}

Spring5ではAsyncRestTemplateは非推奨となっているようです。

使用すべきでないリスト (Spring Framework 6.0.11 API) - Javadoc
Spring Boot の概要から各機能の詳細までが網羅された公式リファレンスドキュメントです。開発者が最初に読むべきドキュメントです。

@EnableAsync,@Asyncアノテーションを使用して非同期処理を実装する

Spring Bootで非同期処理を実装するには@EnableAsyncアノテーションを使用します。

起動クラスに@EnableAsyncアノテーションを付与します。これで@Asyncアノテーションを付与したメソッドが非同期で実行されるようになります。

package jp.co.confrage;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class AsyncControllerApplication {

  public static void main(String[] args) {
    SpringApplication.run(AsyncControllerApplication.class, args);
  }
}

コントローラから非同期処理をするasyncメソッドを3回呼び出しています。指定秒数かかるメソッドです。

package jp.co.confrage.presentation.controller;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import jp.co.confrage.presentation.service.DemoService;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
public class DemoController {

  private final DemoService demoService;

  /**
   * 指定秒数かかるAPI
   *
   * @param seconds1
   * @param seconds2
   * @param seconds3
   * @return
   * @throws InterruptedException
   * @throws ExecutionException
   */
  @RequestMapping(path = "/sleep/{seconds1}/{seconds2}/{seconds3}", method = RequestMethod.GET)
  public ResponseEntity<String> sleep(
      @PathVariable String seconds1, @PathVariable String seconds2, @PathVariable String seconds3)
      throws InterruptedException, ExecutionException {
    long startTime = System.currentTimeMillis();
    CompletableFuture<String> page1 = demoService.async(seconds1);
    CompletableFuture<String> page2 = demoService.async(seconds2);
    CompletableFuture<String> page3 = demoService.async(seconds3);

    CompletableFuture.allOf(page1, page2, page3).join(); // 終了まで待機する

    System.out.println("--> " + page1.get());
    System.out.println("--> " + page2.get());
    System.out.println("--> " + page3.get());
    long endTime = System.currentTimeMillis();
    System.out.println("処理時間:" + (endTime - startTime) + " ms");
    return ResponseEntity.ok("test");
  }
}

@Asyncアノテーションが付与されたメソッドは別スレッドで実行されます。以下サービスクラスです。

package jp.co.confrage.presentation.service;

import java.util.concurrent.CompletableFuture;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class DemoService {

  /**
   * 非同期処理
   *
   * @param seconds
   * @return
   * @throws InterruptedException
   */
  @Async
  public CompletableFuture async(String seconds) throws InterruptedException {
    Thread.sleep(Long.valueOf(seconds) * 1000L);
    return CompletableFuture.completedFuture(seconds + "秒かかる処理");
  }
}

以下実行例です。

curl -X GET http://localhost:8080/sleep/2/3/1

--> 2秒かかる処理
--> 3秒かかる処理
--> 1秒かかる処理
処理時間:3018 ms

@Asyncアノテーションをコメントアウトした場合の実行例です。同期処理されるため、実行時間がかかっています。

curl -X GET http://localhost:8080/sleep/2/3/1

--> 2秒かかる処理
--> 3秒かかる処理
--> 1秒かかる処理
処理時間:6010 ms

以下サイトによるとorg.springframework.core.task.TaskExecutorクラスで色々調整が可能です。

また、実行可能jarでもwarでも実装可能です。

Spring Boot @Async アノテーションで非同期メソッドの作成 - 公式サンプルコード
Spring Boot の概要から各機能の詳細までが網羅された公式リファレンスドキュメントです。開発者が最初に読むべきドキュメントです。

サンプルコード

コメント

タイトルとURLをコピーしました