Spring Frameworkの非同期処理入門

# Spring Frameworkの非同期処理入門

## はじめに

Spring Frameworkでは、`@Async`アノテーションを使用して非同期処理を簡単に実装することができます。この記事では、非同期処理の基本的な実装方法と、実際のユースケースについて解説します。

## 基本的な実装方法

### 1. 非同期処理の有効化

#### AsyncConfigの詳細設定

```java
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        
        // コアプールサイズ：常に維持されるスレッド数
        executor.setCorePoolSize(5);
        
        // 最大プールサイズ：最大で作成されるスレッド数
        executor.setMaxPoolSize(10);
        
        // キューサイズ：待機中のタスクを保持するキューのサイズ
        executor.setQueueCapacity(25);
        
        // スレッドの接頭辞：スレッド名の識別子
        executor.setThreadNamePrefix("AsyncThread-");
        
        // スレッドの優先度
        executor.setThreadPriority(Thread.NORM_PRIORITY);
        
        // コアプールサイズを超えるスレッドの生存時間（ミリ秒）
        executor.setKeepAliveSeconds(60);
        
        // シャットダウン時の動作
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        
        // 拒否ポリシーの設定
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        
        // 初期化
        executor.initialize();
        return executor;
    }
    
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}
```

#### スレッドプールの設定パラメータの説明

1. **コアプールサイズ（Core Pool Size）**
   - 常に維持されるスレッドの数
   - 最小限必要なスレッド数を指定
   - 推奨値：CPUコア数 + 1

2. **最大プールサイズ（Max Pool Size）**
   - 最大で作成されるスレッドの数
   - 負荷に応じてスレッド数を増やす上限
   - 推奨値：コアプールサイズの2倍程度

3. **キューサイズ（Queue Capacity）**
   - 待機中のタスクを保持するキューのサイズ
   - キューが満杯になると新しいスレッドが作成される
   - 推奨値：処理の特性に応じて調整

4. **スレッドの生存時間（Keep Alive Seconds）**
   - コアプールサイズを超えるスレッドの生存時間
   - アイドル状態のスレッドを終了させるまでの時間
   - 推奨値：60秒程度

#### 拒否ポリシー（Rejected Execution Handler）の種類

1. **AbortPolicy（デフォルト）**
   - タスクを拒否し、RejectedExecutionExceptionをスロー
   - 最も厳格なポリシー

2. **CallerRunsPolicy**
   - 呼び出し元のスレッドでタスクを実行
   - システムの過負荷を防ぐ効果がある

3. **DiscardPolicy**
   - タスクを静かに破棄
   - エラーをスローしない

4. **DiscardOldestPolicy**
   - 最も古いタスクを破棄し、新しいタスクを実行
   - 最新のタスクを優先

#### カスタム例外ハンドラーの実装例

```java
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
    
    private static final Logger logger = LoggerFactory.getLogger(CustomAsyncExceptionHandler.class);
    
    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        logger.error("非同期処理で例外が発生しました: " + method.getName(), ex);
        
        // エラー通知の送信
        sendErrorNotification(ex, method, params);
        
        // エラーログの保存
        saveErrorLog(ex, method, params);
    }
    
    private void sendErrorNotification(Throwable ex, Method method, Object... params) {
        // エラー通知の実装
    }
    
    private void saveErrorLog(Throwable ex, Method method, Object... params) {
        // エラーログの保存実装
    }
}
```

#### 複数のスレッドプールの設定例

```java
@Configuration
@EnableAsync
public class MultipleAsyncConfig {
    
    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setThreadNamePrefix("Task-");
        executor.initialize();
        return executor;
    }
    
    @Bean(name = "emailExecutor")
    public Executor emailExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(10);
        executor.setThreadNamePrefix("Email-");
        executor.initialize();
        return executor;
    }
}
```

### 2. 非同期メソッドの実装

```java
@Service
public class AsyncService {
    
    @Async
    public CompletableFuture<String> processAsync(String data) {
        // 重い処理をシミュレート
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return CompletableFuture.completedFuture("Processed: " + data);
    }
    
    @Async
    public void sendEmailAsync(String to, String subject, String body) {
        // メール送信処理
        System.out.println("Sending email to: " + to);
        // 実際のメール送信ロジック
    }
}
```

## ケーススタディ

### 1. バッチ処理の非同期実行

```java
@Service
public class BatchProcessingService {
    
    @Autowired
    private AsyncService asyncService;
    
    public void processBatchData(List<String> dataList) {
        List<CompletableFuture<String>> futures = dataList.stream()
            .map(data -> asyncService.processAsync(data))
            .collect(Collectors.toList());
            
        // すべての処理が完了するのを待つ
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .thenAccept(v -> {
                List<String> results = futures.stream()
                    .map(CompletableFuture::join)
                    .collect(Collectors.toList());
                System.out.println("Batch processing completed: " + results);
            });
    }
}
```

### 2. 通知システムの実装

```java
@Service
public class NotificationService {
    
    @Autowired
    private AsyncService asyncService;
    
    public void sendNotifications(List<User> users, String message) {
        users.forEach(user -> {
            // メール通知
            asyncService.sendEmailAsync(
                user.getEmail(),
                "New Notification",
                message
            );
            
            // プッシュ通知
            asyncService.sendPushNotificationAsync(
                user.getDeviceToken(),
                message
            );
        });
    }
}
```

### 3. ファイル処理の非同期実行

```java
@Service
public class FileProcessingService {
    
    @Async
    public CompletableFuture<ProcessResult> processLargeFile(String filePath) {
        return CompletableFuture.supplyAsync(() -> {
            ProcessResult result = new ProcessResult();
            try {
                // 大きなファイルの処理
                List<String> lines = Files.readAllLines(Paths.get(filePath));
                result.setProcessedLines(lines.size());
                result.setStatus("SUCCESS");
            } catch (IOException e) {
                result.setStatus("ERROR");
                result.setErrorMessage(e.getMessage());
            }
            return result;
        });
    }
}
```

## 実践的な使用例

### 1. レポート生成システム

```java
@Service
public class ReportGenerationService {
    
    @Autowired
    private AsyncService asyncService;
    
    public void generateReports(List<ReportRequest> requests) {
        requests.forEach(request -> {
            @Async
            public CompletableFuture<Report> generateReport(ReportRequest request) {
                // レポート生成処理
                Report report = new Report();
                // データの集計
                // グラフの生成
                // PDFの作成
                return CompletableFuture.completedFuture(report);
            }
        });
    }
}
```

### 2. データ同期システム

```java
@Service
public class DataSyncService {
    
    @Async
    public CompletableFuture<SyncResult> syncData(String source, String destination) {
        return CompletableFuture.supplyAsync(() -> {
            SyncResult result = new SyncResult();
            try {
                // データの同期処理
                // 差分の検出
                // データの転送
                result.setStatus("SUCCESS");
            } catch (Exception e) {
                result.setStatus("ERROR");
                result.setErrorMessage(e.getMessage());
            }
            return result;
        });
    }
}
```

## ベストプラクティス

1. **スレッドプールの適切な設定**
   - コアプールサイズと最大プールサイズの適切な設定
   - キューサイズの考慮
   - スレッド名の設定

2. **例外処理**
   - `AsyncUncaughtExceptionHandler`の実装
   - 非同期処理内での適切な例外処理

3. **リソース管理**
   - スレッドプールの適切なシャットダウン
   - リソースの解放

4. **モニタリング**
   - 非同期処理の状態監視
   - パフォーマンスメトリクスの収集

## まとめ

Spring Frameworkの非同期処理機能を使用することで、アプリケーションのパフォーマンスとスケーラビリティを向上させることができます。適切な実装と設定により、効率的な非同期処理を実現できます。

## 参考資料

- Spring Framework公式ドキュメント
- Java Concurrency in Practice
- Spring Boot Reference Guide