공공부하자개발 · 영어 학습 노트
자바
실무 확장Excel · 파일 업로드 · DB 연동0/22 완료
  • 01Excel(XLSX) 구조와 순수 JDK로 읽기/쓰기
  • 02Apache POI로 Excel 업로드/다운로드
  • 03파일 업로드/다운로드 서버 (HttpServer)
  • 04JDBC 기초와 트랜잭션 (H2)
  • 05MyBatis 어노테이션 매퍼로 쿼리 연동
  • 06MyBatis XML 매퍼 · Oracle 방언 · PageHelper · Spring Boot
  • 07REST API 서버와 JSON
  • 08Vue 3 SPA 와 Java 서버 연동
  • 09@Scheduled 운영
  • 10로깅 실무: 레벨·계층, MDC 추적, 예외·성능, 마스킹, 롤링, JSON 로그
  • 11외부 API 연동
  • 12테스트 실무
  • 13암호화·개인정보 보호
  • 14인코딩·한글 실무
  • 15@Transactional 심화
  • 16긴 작업 비동기 처리와 진행률
  • 17SFTP·FTP 파일 연계
  • 18로컬 캐시와 @Cacheable
  • 19메일·알림 발송
  • 20웹 보안 체크리스트
  • 21빌드 도구와 폐쇄망 의존성 반입
  • 22성능 측정: p50·p95·p99, 측정 계층, JFR, JMH 함정, 자체 부하 테스트, 병목 순위
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 실무 확장 › 09 / 22

@Scheduled 운영

fixedRate·fixedDelay·cron, 예외·겹침·다중 인스턴스·종료
섹션 7진행 0 / 22
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

3. 코드 예제

실행 방법입니다. 외부 jar 는 필요 없습니다.

powershell
cd java-src\extension\09_scheduled
C:\project\jdk-21.0.8\bin\javac -encoding UTF-8 *.java
C:\project\jdk-21.0.8\bin\java -Dstdout.encoding=UTF-8 Main   # 7개 시나리오, 약 6초

예제 1: fixedRate vs fixedDelay

java
// Main.fixedRateVsFixedDelay
AtomicInteger rate = new AtomicInteger(), delay = new AtomicInteger();
Runnable work = () -> sleep(300);                    // 작업 300ms
ses.scheduleAtFixedRate(
        () -> { work.run(); rate.incrementAndGet(); },
        0, 200, TimeUnit.MILLISECONDS);               // 간격 200ms
ses.scheduleWithFixedDelay(
        () -> { work.run(); delay.incrementAndGet(); },
        0, 200, TimeUnit.MILLISECONDS);
sleep(1500);
text
fixedRate  실행 5회 ← 밀린 만큼 연달아 실행(간격 0)
fixedDelay 실행 3회 ← 300+200 = 500ms 마다

작업(300ms)이 간격(200ms)보다 길어서 차이가 드러났습니다. fixedRate 는 밀린 회차를 쌓아 두고 연달아 소진하고, fixedDelay 는 항상 500ms 를 지킵니다.

예제 2: 예외가 스케줄을 죽인다

java
// Main.exceptionKillsSchedule
Runnable boom = () -> {
    if (raw.incrementAndGet() == 3)
        throw new IllegalStateException("DB 연결 끊김");
};
ScheduledFuture<?> f = ses.scheduleAtFixedRate(
        boom, 0, 100, TimeUnit.MILLISECONDS);
SafeTask st = new SafeTask("safe",
        () -> { if (safe.incrementAndGet() == 3)
                    throw new IllegalStateException("DB 연결 끊김"); },
        Duration.ofMillis(50));
ses.scheduleAtFixedRate(st, 0, 100, TimeUnit.MILLISECONDS);
sleep(1000);
text
날것    실행 3회, isDone=true ← 3회 후 영구 정지, 로그 없음
숨어 있던 예외: f.get() → java.lang.IllegalStateException: DB 연결 끊김
safe: 성공 9, 건너뜀 0, 실패 1 ← 실패해도 다음 회차 계속

SafeTask.run 전체는 짧습니다.

java
// SafeTask.run
@Override
public void run() {
    if (!running.compareAndSet(false, true)) {  // 이전 회차 진행 중
        skips.incrementAndGet();
        log("SKIP  이전 실행이 아직 진행 중");
        return;
    }
    Instant start = Instant.now();
    try {
        body.run();
        runs.incrementAndGet();
        Duration took = Duration.between(start, Instant.now());
        log((took.compareTo(slowThreshold) > 0 ? "SLOW  " : "OK    ")
                + took.toMillis() + "ms");
    } catch (Throwable t) {                      // Error 까지 잡아야 안 죽는다
        failures.incrementAndGet();
        log("FAIL  " + t);
    } finally {
        running.set(false);
    }
}

AtomicBoolean running 이 겹침 방지, catch (Throwable t) 가 예외 방지, finally 가 다음 회차를 위한 원복입니다. 세 줄짜리 아이디어를 한 클래스에 모아 두면 모든 배치에 재사용할 수 있습니다.

예제 3: 스레드 풀 크기가 지연을 만든다

java
// Main.singleThreadBlocksOthers
for (int threads : new int[]{1, 4}) {
    ScheduledExecutorService ses =
            Executors.newScheduledThreadPool(threads, named("sch"));
    ses.scheduleAtFixedRate(() -> sleep(800),
            0, 1000, TimeUnit.MILLISECONDS);       // 느린 작업
    ses.scheduleAtFixedRate(() -> {                // 빠른 작업, 간격을 측정
        long now = System.currentTimeMillis();
        maxGap[0] = Math.max(maxGap[0], now - last[0]);
        last[0] = now;
    }, 0, 100, TimeUnit.MILLISECONDS);
    sleep(1000);
}
text
스레드 1개: 빠른 작업 10회, 최대 간격 817ms ← 느린 작업 뒤에 갇혔다가 몰아서 실행
스레드 4개: 빠른 작업 11회, 최대 간격 119ms ← 100ms 간격 유지

스레드 1개 풀에서는 느린 작업이 스레드를 점유한 800ms 동안 빠른 작업이 실행되지 못하고 밀립니다. 스레드를 4개로 늘리면 서로 방해하지 않습니다.

예제 4: 겹침 방지 SKIP

java
// Main.overlapGuard
SafeTask task = new SafeTask("report", () -> sleep(250),
        Duration.ofMillis(500));
ScheduledExecutorService a =
        Executors.newSingleThreadScheduledExecutor(named("A"));
ScheduledExecutorService b =
        Executors.newSingleThreadScheduledExecutor(named("B"));
a.scheduleAtFixedRate(task, 0, 300, TimeUnit.MILLISECONDS);
b.scheduleAtFixedRate(task, 100, 300, TimeUnit.MILLISECONDS);
sleep(600);
text
[B-1] report     SKIP  이전 실행이 아직 진행 중
[A-1] report     OK    259ms
[B-1] report     SKIP  이전 실행이 아직 진행 중
[A-1] report     OK    267ms
[A-1] report     OK    15ms
report: 성공 3, 건너뜀 2, 실패 0

같은 SafeTask 인스턴스를 스케줄러 A·B 가 각각 돌렸습니다. running 플래그가 두 스케줄러에 공유되므로, A 가 실행 중일 때 B 의 회차는 SKIP 됩니다. 인스턴스는 하나인데 스케줄러가 둘인 상황을 흉내 낸 예제입니다.

예제 직접 실행

아래 폴더를 JDK 21 로 컴파일하고 실행합니다. 외부 jar 를 쓰는 레슨은 java-src/lib 를 클래스패스에 넣습니다.

cd java-src\extension\09_scheduled
javac -encoding UTF-8 *.java && java Main

:: 외부 jar 가 필요한 레슨
javac -encoding UTF-8 -cp "..\..\lib\*;." *.java && java -cp "..\..\lib\*;." Main
코드 예제
  • 예제 1: fixedRate vs fixedDelay
  • 예제 2: 예외가 스케줄을 죽인다
  • 예제 3: 스레드 풀 크기가 지연을 만든다
  • 예제 4: 겹침 방지 SKIP
이전 섹션2 핵심 원리3 / 7다음 섹션4 응용 변형 예제