본문 바로가기

Diary/삽질노트

[Android] Thread-safe 하지 않은 SimpleDateFormat

프로젝트를 작업하는 중에 서버에서 날짜의 형태를 UTC 포맷으로 내려주고있어서 변환이 필요했다. 대부분 UTC 포맷으로 내려오고 자주 사용될 여지가 있다고 생각해서 Dateutils.kt 파일에 global function 으로 UTC 관련 유틸을 만들어 사용하고있었다.

typealias UTC = String

private const val defaultUTCFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"

private val utcDateFormat = SimpleDateFormat(defaultUTCFormat, Locale.ROOT).apply {
    timeZone = TimeZone.getTimeZone("UTC")
}

private const val defaultKSTFormat = "yyyy.MM.dd"

private val kstDateFormat = SimpleDateFormat(defaultKSTFormat, Locale.KOREA).apply {
    timeZone = TimeZone.getTimeZone("GMT+09:00")
}

 

여기서 위와 같이 SimpleDateFormat 을 글로벌 멤버변수(static 하게)로 쓰고있었는데, 파싱된 데이터에 이상한 숫자가 들어가있는 것을 발견했다. 예를 들면 2020.09.01 이라고 들어가있어야하는데, 0008.00.10 와 같이 말이 안되는 데이터가 들어가있었다.

더보기

코틀린 파일(.kt)에 정의된 글로벌 변수, 메서드는 static 멤버이다. 따라서 해당 멤버를 사용하는 스레드들은 static 멤버를 공유하게 된다.

 

처음에는 서버에서 값을 잘못주고있나? 싶어서 로그도 찍어보고 브레이크 포인트를 걸어 확인해봤는데 그것도 아니였다.

원인은 바로 여러 스레드에서 해당 포맷을 접근하는 과정에 데이터가 꼬여서 발생하는 문제였다..!

딱히 우리 플젝에서는 multi thread 환경을 고려해야하는 상황이 없는데, 예외적으로 여러 스레드가 동시에 하는 작업이 있었다. (스택 아래에 깔려 pause 상태에 있는 액티비티 2개가 구독하는 Observable 에 이벤트가 발생 - io 스레드) 구조상 문제일 수도 있겠지만, 정의된 이벤트가 언제 어떻게 동작하는지 시점을 제대로 캐치하지 못해 예상하지 못한 케이스였던것이다.

그래서 그냥 화면별로 SimpleDateFormat 인스턴스를 생성하고 위 유틸로 넘기도록 수정했다. DateFormat 객체는 thread-safe 하지 않다는 것과 이벤트가 동시에 발생해서 작업이 시작된다는걸 인지하고 있었다면 내 아까운 시간을 날리지 않았을텐데....(그냥 내 탓 🤦‍♀️)

그리고 자바8에서는 새로운 LocalDate 클래스를 이용해 thread-safe 하게 작업할 수 있다고한다. 자바8을 쓸 수 있다면 이걸 이용해보는 것도 나쁘지 않을 듯 싶다.

 

참고하면 좋은 것들

Why is Java's SimpleDateFormat not thread-safe?

자바8의 java.time 패키지(LocalDate, LocalTime, LocalDateTime 등)