✅ はじめに
Spring Bootで「定期的な処理」を実行したいとき、最もシンプルに実装できる方法が @Scheduled
アノテーションです。
このガイドでは、
@Scheduled
の使い方- cron式やdelayの違い
- 複数メソッドの挙動(並列 vs. 順番待ち)
- スケジューラの並列化設定方法
など、現場で使える知識を丁寧に解説します!
✅ 事前準備:スケジューリングを有効にする
まず、@Scheduled
を使うには明示的にスケジューラ機能を有効化する必要があります。
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling // ← これを忘れると定期処理が動かない!
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
✅ 定期実行処理の作成方法(@Scheduledの基本)
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class SampleScheduler {
@Scheduled(fixedRate = 5000) // 5秒ごとに実行
public void runTask() {
System.out.println("実行中: " + java.time.LocalTime.now());
}
}
✅ Bean登録されているクラスである必要がある
@Scheduled
を付けたメソッドは、Spring管理下の「Bean」でなければ実行されません。
🔍 Beanって何?
Beanとは、SpringがDI(依存性注入)の対象として管理するインスタンスのこと。アプリの起動時に自動的に生成・登録されます。
🔧 Beanとして認識されるには?
@Component
@Service
@Repository
@Configuration
などのアノテーションを付けることで、自動でBean登録されます。
@Component // これがないと動かない!
public class MyScheduler {
@Scheduled(fixedRate = 10000)
public void run() {
System.out.println("10秒ごとに実行!");
}
}
✅ 各種 @Scheduled のオプションと違い
🔁 fixedRate:開始時間からの間隔で実行(非同期でも可)
@Scheduled(fixedRate = 5000)
public void task1() {}
🔁 fixedDelay:前回処理の「終了から○ms後」に実行
@Scheduled(fixedDelay = 5000)
public void task2() {}
⏱ initialDelay:最初だけ遅らせてから開始
@Scheduled(initialDelay = 3000, fixedRate = 10000)
public void task3() {}
✅ cron式を使った柔軟な定期処理
@Scheduled(cron = "0 0 0 * * *") // 毎日0時に実行
public void runDaily() {}
曜日 | 指定方法 |
---|---|
月〜日 | MON〜SUN |
毎分 | 0 * * * * * |
毎週月曜9時 | 0 0 9 * * MON |
✅ デフォルトは単一スレッド!複数メソッドは順番待ち!
Spring Bootでは、@Scheduled
メソッドはすべて単一のスレッドで実行されます。
つまり?
複数の @Scheduled
メソッドが同じタイミングで起動しても、一つずつしか実行されません。
@Scheduled(fixedRate = 1000)
public void taskA() {
System.out.println("タスクA開始");
Thread.sleep(3000); // 時間かかる処理
System.out.println("タスクA終了");
}
@Scheduled(fixedRate = 1000)
public void taskB() {
System.out.println("タスクB実行");
}
🧨 上記の例では…
taskA
が3秒かかると、taskB
の実行は そのあと(順番待ち) になる。
❗ これは意図しない「実行遅延」や「詰まり」の原因になります。
✅ 8. スケジューラを並列化(マルチスレッド)する方法【詳細解説】
🔒 デフォルトでは「シングルスレッド」
Spring Bootの @Scheduled
は、特に設定しない場合、1本のスレッドしか使われません。
そのため、複数の @Scheduled
メソッドが同じタイミングで発火しても、順番に1つずつしか処理されないのです。
🧪 例:2つのタスクが重なると…
@Scheduled(fixedRate = 1000)
public void longTask() throws InterruptedException {
System.out.println("LongTask START");
Thread.sleep(5000); // 処理に5秒かかる
System.out.println("LongTask END");
}
@Scheduled(fixedRate = 1000)
public void quickTask() {
System.out.println("QuickTask");
}
実行結果(一部)
LongTask START
(1秒後) ← QuickTask は実行されない
(5秒後)LongTask END
QuickTask ← ここでやっと実行!
👉 QuickTask
が「後回し」になり、定期実行にならない!
✅ 解決策:ThreadPoolTaskScheduler を使って並列化
SchedulingConfigurer
インタフェースを使い、スケジューラのスレッドプールを自分で定義すれば、複数タスクを同時に実行できます。
🔧 実装例:SchedulerConfigクラス
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5); // 並列に最大5スレッドまで処理可能
scheduler.setThreadNamePrefix("scheduled-task-");
scheduler.initialize();
registrar.setTaskScheduler(scheduler);
}
}
✅ 効果:
- 各
@Scheduled
メソッドが同時に別スレッドで実行される - 時間がかかる処理が他の定期タスクをブロックしない
✅ 重要ポイント
項目 | 説明 |
---|---|
setPoolSize(int) | 並列で実行できる最大スレッド数(多すぎ注意) |
setThreadNamePrefix | ログなどでスレッド名が見えるようにすると便利 |
initialize() | スケジューラを起動する前に必ず呼ぶ |
registrar.setTaskScheduler() | 作成したスケジューラをSpringに登録 |
📈 スケーラブルな設計になる
この方法を使えば:
- 各
@Scheduled
メソッドが独立して並列に実行可能 - 処理が重いスケジューラが他に影響しない
- 複数のタスクを同時に定期実行できる
⚠️ スレッドを増やすとサーバーにどれくらい負荷がかかるのか?
ThreadPoolTaskScheduler
の setPoolSize()
でスレッド数を増やすことで、確かに並列実行が可能になりますが、次のようなリスクや注意点もあります。
🔻 1. メモリ消費が増える
- スレッド1つあたり、1MB〜数MB程度のスタックメモリを消費します。
- 10スレッドなら約10MB〜30MB、100スレッドなら数百MB以上に。
- サーバーのメモリが潤沢でない場合、OutOfMemoryErrorのリスクも。
🔻 2. CPUへの負荷が高くなる
- スレッド数を増やしても、CPUコア数を超えると実行は待機状態に。
- 例えば、4コアのCPUに20スレッドを同時起動させても、4つしか実行されず16は待ち状態。
- 無駄にスレッドを増やすとコンテキストスイッチ(切り替え)コストが増えるため、逆に処理が遅くなる可能性あり。
🔻 3. スレッド間の同期・ロックが複雑化する
- 並列化が進むと、スレッド間で同じリソースにアクセスする場面が出てきます。
- その結果:
- データ競合(race condition)
- ロックによるデッドロック
- 排他制御によるパフォーマンス低下
- ➜ マルチスレッドにするほど設計とテストが難しくなる
✅ 適切なスレッド数の目安
サーバー構成 | おすすめのスレッド数(例) |
---|---|
ローカルPC(4コア/8GB) | 2〜4程度 |
小規模サーバー(2vCPU/4GB) | 2〜3程度 |
商用サーバー(8vCPU/16GB〜) | 5〜10程度(要チューニング) |
💡 重要:PoolSize = コア数 × 2
程度を上限の目安とするのが一般的です
✅ スレッド数を増やす際のチェックポイント
- ✅ 並列処理する「タスク」の処理時間と重さを見積もる
- ✅ アプリケーションが使用する他の非同期処理(例:@Async, ExecutorService)とバランスをとる
- ✅ JVMの最大メモリやCPUコア数に合わせて上限を設定
- ✅ 本番環境ではモニタリング(JVM, CPU, Heap, スレッド数)を必ず導入
✅ 9. よくあるトラブルと解決法まとめ
問題 | 原因 |
---|---|
@Scheduled が動かない | @EnableScheduling を忘れている |
メソッドが呼ばれない | @Component などが付いておらず Bean登録されていない |
処理が遅延する・詰まる | 単一スレッドで順番待ち → スケジューラの並列化が必要 |
時間がズレる | cron式が UTC基準になっている → タイムゾーン指定が必要 |
✅ 10. まとめ
項目 | ポイント |
---|---|
@Scheduled を使うには | @EnableScheduling が必要 |
Bean登録が必須 | @Component や @Service をつけよう |
デフォルトは1スレッド | 複数メソッドで詰まりが発生 → 並列化設定が有効 |
並列実行が必要なら | ThreadPoolTaskScheduler をカスタム設定 |
cron式 | 毎日・毎時など柔軟なスケジュールが可能 |
コメント