본문 바로가기

Java

SimpleDateForat 동시성 이슈

 

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