LANGUAGE/!$%!% ERROR NOTE 2021. 4. 4. 00:49

[Java] SimpleDateFormat

Not Thread Safe한 SimpleDateFormat을 잘못 사용하면 오류가 발생한다.

1. Error - 오류

다음과 같이 Test해보았다.

  • Test 구성

  • Test 방법

    • ※ 같은 Test Code로 10번정도 실행해 보았는데 다른 유형의 Exception들이 임의로 발생하였습니다.
  • Error

    • java.lang.NumberFormatException: For input string: ""
    • java.lang.NumberFormatException: For input string: ".153E2153E2"
    • java.lang.NumberFormatException: For input string: "E.4230015E
    • java.lang.NumberFormatException: empty String
    • java.lang.NumberFormatException: multiple points
    • java.lang.ArrayIndexOutOfBoundsException: -1
  • ※ 오류 상세는 다음과 같습니다. (출력된 Excetion들의 StackTrace정보를 긁어 모아 봤습니다.)

    java.util.concurrent.ExecutionException: java.lang.NumberFormatException: For input string: "E.4230015E"
          at java.util.concurrent.FutureTask.report(FutureTask.java:122)
          at java.util.concurrent.FutureTask.get(FutureTask.java:192)
          at com.others.test.SimpleDateFormatTest.SimpleDateFormatIsNotSafeTest(SimpleDateFormatTest.java:35)
          at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
          at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
          at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
          at java.lang.reflect.Method.invoke(Method.java:498)
          at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
          at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
          at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
          at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
          at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:19)
          at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
          at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
          at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
          at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
          at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
          at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
          at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
          at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
          at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
          at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
          at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
          at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
          at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
          at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
      Caused by: java.lang.NumberFormatException: For input string: "E.4230015E"
          at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
          at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
          at java.lang.Double.parseDouble(Double.java:538)
          at java.text.DigitList.getDouble(DigitList.java:169)
          at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
          at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
          at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
          at java.text.DateFormat.parse(DateFormat.java:364)
          at com.others.test.SimpleDateFormatTest$1.call(SimpleDateFormatTest.java:53)
          at com.others.test.SimpleDateFormatTest$1.call(SimpleDateFormatTest.java:49)
          at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
          at java.util.concurrent.FutureTask.run(FutureTask.java)
          at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
          at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
          at java.lang.Thread.run(Thread.java:748)
    java.util.concurrent.ExecutionException: java.lang.NumberFormatException: empty String
          at java.util.concurrent.FutureTask.report(FutureTask.java:122)
          at java.util.concurrent.FutureTask.get(FutureTask.java:192)
          at com.others.test.SimpleDateFormatTest.SimpleDateFormatIsNotSafeTest(SimpleDateFormatTest.java:35)
          at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
          at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
          at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
          at java.lang.reflect.Method.invoke(Method.java:498)
          at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
          at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
          at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
          at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
          at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:19)
          at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
          at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
          at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
          at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
          at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
          at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
          at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
          at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
          at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
          at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
          at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
          at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
          at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
          at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
      Caused by: java.lang.NumberFormatException: empty String
          at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
          at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
          at java.lang.Double.parseDouble(Double.java:538)
          at java.text.DigitList.getDouble(DigitList.java:169)
          at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
          at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1867)
          at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
          at java.text.DateFormat.parse(DateFormat.java:364)
          at com.others.test.SimpleDateFormatTest$1.call(SimpleDateFormatTest.java:53)
          at com.others.test.SimpleDateFormatTest$1.call(SimpleDateFormatTest.java:49)
          at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
          at java.util.concurrent.FutureTask.run(FutureTask.java)
          at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
          at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
          at java.lang.Thread.run(Thread.java:748)
    java.util.concurrent.ExecutionException: java.lang.ArrayIndexOutOfBoundsException: -1
          at java.util.concurrent.FutureTask.report(FutureTask.java:122)
          at java.util.concurrent.FutureTask.get(FutureTask.java:192)
          at com.others.test.SimpleDateFormatTest.SimpleDateFormatIsNotSafeTest(SimpleDateFormatTest.java:35)
          at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
          at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
          at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
          at java.lang.reflect.Method.invoke(Method.java:498)
          at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
          at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
          at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
          at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
          at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:19)
          at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
          at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
          at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
          at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
          at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
          at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
          at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
          at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
          at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
          at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
          at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
          at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
          at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
          at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
      Caused by: java.lang.ArrayIndexOutOfBoundsException: -1
          at java.text.DigitList.fitsIntoLong(DigitList.java:230)
          at java.text.DecimalFormat.parse(DecimalFormat.java:2082)
          at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1867)
          at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
          at java.text.DateFormat.parse(DateFormat.java:364)
          at com.others.test.SimpleDateFormatTest$1.call(SimpleDateFormatTest.java:53)
          at com.others.test.SimpleDateFormatTest$1.call(SimpleDateFormatTest.java:49)
          at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
          at java.util.concurrent.FutureTask.run(FutureTask.java)
          at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
          at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
          at java.lang.Thread.run(Thread.java:748)
    java.util.concurrent.ExecutionException: java.lang.NumberFormatException: multiple points
          at java.util.concurrent.FutureTask.report(FutureTask.java:122)
          at java.util.concurrent.FutureTask.get(FutureTask.java:192)
          at com.others.test.SimpleDateFormatTest.SimpleDateFormatIsNotSafeTest(SimpleDateFormatTest.java:35)
          at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
          at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
          at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
          at java.lang.reflect.Method.invoke(Method.java:498)
          at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
          at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
          at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
          at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
          at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:19)
          at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
          at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
          at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
          at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
          at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
          at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
          at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
          at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
          at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
          at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
          at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
          at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
          at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
          at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
      Caused by: java.lang.NumberFormatException: multiple points
          at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
          at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
          at java.lang.Double.parseDouble(Double.java:538)
          at java.text.DigitList.getDouble(DigitList.java:169)
          at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
          at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
          at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
          at java.text.DateFormat.parse(DateFormat.java:364)
          at com.others.test.SimpleDateFormatTest$1.call(SimpleDateFormatTest.java:53)
          at com.others.test.SimpleDateFormatTest$1.call(SimpleDateFormatTest.java:49)
          at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
          at java.util.concurrent.FutureTask.run(FutureTask.java)
          at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
          at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
          at java.lang.Thread.run(Thread.java:748)
    java.util.concurrent.ExecutionException: java.lang.NumberFormatException: For input string: ".153E2153E2" 
        at java.util.concurrent.FutureTask.report(FutureTask.java:122) 
        at java.util.concurrent.FutureTask.get(FutureTask.java:192) 
        at com.others.test.SimpleDateFormatTest.SimpleDateFormatIsNotSafeTest(SimpleDateFormatTest.java:35) 
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
        at java.lang.reflect.Method.invoke(Method.java:498) 
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) 
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) 
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) 
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 
        at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:19) 
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) 
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) 
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) 
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) 
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) 
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) 
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) 
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) 
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363) 
        at org.junit.runner.JUnitCore.run(JUnitCore.java:137) 
        at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) 
        at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33) 
        at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220) 
        at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53) 
    Caused by: java.lang.NumberFormatException: For input string: ".153E2153E2" 
        at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043) 
        at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110) 
        at java.lang.Double.parseDouble(Double.java:538) 
        at java.text.DigitList.getDouble(DigitList.java:169) 
        at java.text.DecimalFormat.parse(DecimalFormat.java:2089) 
        at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162) 
        at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) 
        at java.text.DateFormat.parse(DateFormat.java:364) 
        at com.others.test.SimpleDateFormatTest$1.call(SimpleDateFormatTest.java:53) 
        at com.others.test.SimpleDateFormatTest$1.call(SimpleDateFormatTest.java:49) 
        at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266) 
        at java.util.concurrent.FutureTask.run(FutureTask.java) 
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) 
        at java.lang.Thread.run(Thread.java:748)
    java.util.concurrent.ExecutionException: java.lang.NumberFormatException: For input string: "" 
        at java.util.concurrent.FutureTask.report(FutureTask.java:122) 
        at java.util.concurrent.FutureTask.get(FutureTask.java:192) 
        at com.others.test.SimpleDateFormatTest.SimpleDateFormatIsNotSafeTest(SimpleDateFormatTest.java:30) 
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
        at java.lang.reflect.Method.invoke(Method.java:498) 
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) 
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) 
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) 
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 
        at org.junit.internal.runners.statements.ExpectException.evaluate(ExpectException.java:19) 
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) 
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) 
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) 
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) 
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) 
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) 
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) 
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) 
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363) 
        at org.junit.runner.JUnitCore.run(JUnitCore.java:137) 
        at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) 
        at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33) 
        at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220) 
        at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53) 
    Caused by: java.lang.NumberFormatException: For input string: "" 
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) 
        at java.lang.Long.parseLong(Long.java:601) at java.lang.Long.parseLong(Long.java:631) 
        at java.text.DigitList.getLong(DigitList.java:195) at java.text.DecimalFormat.parse(DecimalFormat.java:2084) 
        at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1867) 
        at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) at java.text.DateFormat.parse(DateFormat.java:364) 
        at com.others.test.SimpleDateFormatTest$1.call(SimpleDateFormatTest.java:79) 
        at com.others.test.SimpleDateFormatTest$1.call(SimpleDateFormatTest.java:75) 
        at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266) 
        at java.util.concurrent.FutureTask.run(FutureTask.java) 
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) 
        at java.lang.Thread.run(Thread.java:748)

2. Problem - 문제

SimpleDateFormat은 Thread Safe(쓰레드 안전)하지 않습니다.

Not Thread Safe 입니다.

SimpleDateFormat의 parse 메소드는 내부 필드를 사용하여 작업합니다.

때문에 동시에 여러 Thread가 접근하면 작업이 꼬입니다.

SimpleDateFormat Not Thread Safe하다는 것을 모르는 개발자는

개발단계에서 단순한 Test로는 해당 오류를 발견하지 못 했을 수 있습니다.

3. Solved - 해결

※ Case

어떤 회사에서는 MyBatis 사용시 Date 타입의 자동변환을 위해 TypeHandler<Date>을 구현하였는데 단 SimpleDateFormat을 단 1개만 생성하여 final로 정의하여 사용하고 있었습니다.

(10년이 넘게 말이죠..)

@MappedTypes(Date.class)
@MappedJdbcTypes({JdbcType.VARCHAR, JdbcType.CHAR})
public class DateTypeHandler implements TypeHandler<Date> {

    private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public Date getResult(ResultSet rs, int columnIndex) throws SQLException {
        String value = rs.getString(columnIndex);
        return toDate(value);
    }

    private Date toDate(String value){
        ...
        return dateFormat.parse(value);        
    }


}

위는 dateFormat이라는 필드가 여러 Thread에 의해 동시에 접근될 가능성이 있는 Code입니다.

아마도 이전에는 오류를 보고 받았더라도 고객의 잘못된 데이터입력으로 착각했을 것 같습니다.

위의 Code를 (아무도 알아주진 않지만) 스윽 고쳐두었습니다.

프로젝트 중에 발견하고 급해서 ''방법으로 처리해 두었고

기억하고 있다가 3 ~ 4개월이 지나 프로젝트 종료즈음해서

본 Post도 정리하고 ''방법으로 가장 속도가 빠른 방법으로 개선하였습니다.

방법. 가

new SimpleDateFormat()으로 매번 Instance를 생성한다.

  • 예)
    private Date toDate(String value){ 
        ... 
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
        return dateFormat.parse(value); 
    }

방법. 나

synchronized 를 사용하여 Blocking한다.

  • 예)
    private Date toDate(String value){ 
        ... 
        Date result = null; 
        synchronized { 
            result = dateFormat.parse(value); 
        } 
        return result; 
    }

방법. 다

ThreadLocal<DateFormat> 을 사용한다.

  • 예)

    ThreadLocal<DateFormat> format = new ThreadLocal<DateFormat>(){
        @Override protected DateFormat initialValue() { 
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
        } 
    }; 
    
    private Date toDate(String value){ 
        ... 
        return format.get().parse(value); 
    }

마치며..

위의 '가' ~ '다', 4가지 방법 가운데 가장 빠른건 , ThreadLocal<DateFormat>을 활용하는 것입니다.

ThreadLocal을 사용하면 각 쓰레드마다의 new SimpleDateFormat(..) 이 생성되고 사용되어집니다.

(ref: https://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html)

근데 SimpleDateFormat을 왜 그렇게 설계했을까?

말 그대로 Simple하게 쓰고 버리도록 설계한 것 아닐까?..

4. Reference - 참조