SimpleDateFormat 객체를 사용하다 동시성 이슈를 만났다.
이슈를 해결한 과정을 기록해 두려고 한다.
문제
Jackson Library Custom Deserializer에서 SimpleDateFormat을 이용해 역직렬화 작업 수행
간헐적으로 역직렬화 실패하는 이슈 발생
java.lang.NumberFormatException: For input string: ".206E206E22"
at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2054)
at java.base/jdk.internal.math.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.base/java.lang.Double.parseDouble(Double.java:651)
at java.base/java.text.DigitList.getDouble(DigitList.java:169)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2202)
at java.base/java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2244)
...
원인
Java의 SimpleDateFormat이 Thread-safe 하지 않아 여러 스레드에서 CustomDeserializer 이용하면 동시성 이슈 발생
Synchronization
Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.
ref - https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html
SimpleDateFormat (Java Platform SE 8 )
Parses text from a string to produce a Date. The method attempts to parse text starting at the index given by pos. If parsing succeeds, then the index of pos is updated to the index after the last character used (parsing does not necessarily use all charac
docs.oracle.com
재현 Test Code
public class ConcurrencyIssueTest {
private static final SimpleDateFormat SIMPLE_DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
@DisplayName("동시성 이슈 발생")
@Test
void synchronizedIssueOccurred() throws InterruptedException {
int threadCount = 100; // 동시 실행할 스레드 개수
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
CountDownLatch latch = new CountDownLatch(threadCount);
String dateString = "2025-02-06T07:13:08.515+00:00";
for (int i = 0; i < threadCount; i++) {
executorService.submit(() -> {
try {
Date date = SIMPLE_DATE_FORMATTER.parse(dateString);
System.out.println(Thread.currentThread().getName() + " -> Parsed Date: " + date);
} catch (Exception e) {
e.printStackTrace();
} finally {
latch.countDown();
}
});
}
latch.await(); // 모든 스레드가 끝날 때까지 대기
executorService.shutdown();
}
}
SimpleDateFormat parsing 과정에서 mutable한 객체를 사용해 스레드간 동시성 이슈가 생기는 것으로 보인다.
해결책
DateTimeForamtter를 이용하면 동시성 이슈를 해결할 수 있다.
Test Code
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
@DisplayName("동시성 이슈 발생하지 않는다.")
@Test
void synchronizedPerfectly() throws InterruptedException {
int threadCount = 100; // 동시 실행할 스레드 개수
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
CountDownLatch latch = new CountDownLatch(threadCount);
String dateString = "2025-02-06T07:13:08.515+00:00";
for (int i = 0; i < threadCount; i++) {
executorService.submit(() -> {
try {
Date date = Date.from(OffsetDateTime.parse(dateString, DATE_TIME_FORMATTER).toInstant());
System.out.println(Thread.currentThread().getName() + " -> Parsed Date: " + date);
} catch (Exception e) {
e.printStackTrace();
} finally {
latch.countDown();
}
});
}
latch.await(); // 모든 스레드가 끝날 때까지 대기
executorService.shutdown();
}
'Java' 카테고리의 다른 글
Java 시간관련 객체 정리 (0) | 2024.03.09 |
---|---|
람다식 (0) | 2022.05.29 |
익명 클래스 (0) | 2022.05.15 |
Iterator (0) | 2022.04.24 |