개발/자바

SimpleDateFormat Thread-Safe 문제

피터JK 2025. 2. 19. 12:51
728x90

SimpleDateFormat은 내부적으로 공유 상태(Shared State)를 가지기 때문에 멀티스레드 환경에서 동시 접근 시 데이터 충돌이 발생할 수 있음.


🔥 문제 상황 예제 (멀티스레드 환경에서 발생하는 오류)

다음은 여러 스레드에서 동시에 SimpleDateFormat을 공유할 경우 발생할 수 있는 문제를 보여줍니다.

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SimpleDateFormatExample {
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 10; i++) {
            executorService.submit(() -> {
                String formattedDate = sdf.format(new Date());
                System.out.println(Thread.currentThread().getName() + " - " + formattedDate);
            });
        }

        executorService.shutdown();
    }
}

예상 출력 (정상적으로 보일 수도 있음)

pool-1-thread-1 - 2025-02-19 16:30:01
pool-1-thread-2 - 2025-02-19 16:30:01
...

문제점:
멀티스레드 환경에서 SimpleDateFormat이 공유되면 내부적으로 같은 Date 객체를 참조하여 엉뚱한 결과가 출력되거나 ParseException, NumberFormatException 같은 오류가 발생할 수 있음.


해결 방법 1: ThreadLocal 사용 (스레드별 인스턴스 생성)

각 스레드가 자신만의 SimpleDateFormat 인스턴스를 가지도록 ThreadLocal을 사용하면 문제를 방지할 수 있음.

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalExample {
    private static final ThreadLocal<SimpleDateFormat> threadLocalSdf =
        ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 10; i++) {
            executorService.submit(() -> {
                SimpleDateFormat sdf = threadLocalSdf.get(); // 각 스레드별 독립된 인스턴스
                String formattedDate = sdf.format(new Date());
                System.out.println(Thread.currentThread().getName() + " - " + formattedDate);
            });
        }

        executorService.shutdown();
    }
}

출력 예시 (안정적으로 동작함)

pool-1-thread-1 - 2025-02-19 16:35:10
pool-1-thread-2 - 2025-02-19 16:35:10
...

✔️ ThreadLocal을 사용하면 각 스레드마다 독립된 SimpleDateFormat 인스턴스를 보장하기 때문에 스레드 충돌이 발생하지 않음.


해결 방법 2: DateTimeFormatter (Java 8 이상)

Java 8 이상이라면 DateTimeFormatter를 사용하는 것이 최선입니다.
이 클래스는 불변(Immutable)하고 Thread-Safe하게 설계됨.

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class DateTimeFormatterExample {
    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 10; i++) {
            executorService.submit(() -> {
                String formattedDate = LocalDateTime.now().format(formatter);
                System.out.println(Thread.currentThread().getName() + " - " + formattedDate);
            });
        }

        executorService.shutdown();
    }
}

출력 예시 (Thread-Safe)

pool-1-thread-1 - 2025-02-19 16:40:30
pool-1-thread-2 - 2025-02-19 16:40:30
...

✔️ Java 8 이상이라면 DateTimeFormatter를 사용하여 ThreadLocal 없이 안전하게 날짜를 포맷할 수 있음.


🔥 정리

방법 Thread-Safe 여부  장점 단점
SimpleDateFormat 공유 간단한 코드 멀티스레드 환경에서 충돌 발생 가능
ThreadLocal<SimpleDateFormat> Java 7 이하에서도 Thread-Safe 코드가 다소 복잡
DateTimeFormatter (Java 8 이상) 간결하고 Thread-Safe Java 8 이상에서만 사용 가능

🔷 추천

  1. Java 8 이상이면 DateTimeFormatter 사용 (✅ 가장 권장)
  2. Java 7 이하에서는 ThreadLocal<SimpleDateFormat> 사용

즉, Java 7 이하에서는 ThreadLocal을 활용해 스레드별 독립된 SimpleDateFormat 인스턴스를 유지하는 것이 가장 안전한 방법입니다. 

728x90