Not Synchronized SimpleDateFormat
JAVA

Not Synchronized SimpleDateFormat

반응형

SimpleDateFormat을 사용해서 Date를 String으로 format하거나 String을  Date로 파싱할 때 사용한다.

 

public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd");

@SneakyThrows
public static void main(String args[]) {
    Date parse = sdf.parse("2021-07-24");
    String format = sdf.format(new Date());

    System.out.println(parse);
    System.out.println(format);
}

출력 결과

Sun Jan 24 00:07:00 KST 2021
2021-14-18

 

 

특히 계속 사용되는 SimpleDateFormat을 계속 인스턴스를 만들어서 사용하기보다 한번 static 하게 만들어서 사용을 많이 한다. 나도 마찬가지로 위에 코드처럼 선언해서 사용했었는데 운영환경에서 아래와 같은 에러를 받았다.

 

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:2056)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at com.github.sejoung.codetest.simpledateformat.SimpleDateFormatThreadUnsafetyExample.parseDate(SimpleDateFormatThreadUnsafetyExample.java:45)
    at com.github.sejoung.codetest.simpledateformat.SimpleDateFormatThreadUnsafetyExample.lambda$main$0(SimpleDateFormatThreadUnsafetyExample.java:35)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    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)

다행히 예외처리를 잘 해놔서 실제 운영에서는 기본값으로 잘 동작하였지만 근근히 에러로그가 찍히고 있어 원인을 찾아봤는데 원인은 static하게 선언했던 SimpleDateFormat에 있었다.

 

 

https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html

문서 내용을 확인해보면 SimpleDateFormat은 not synchronized하기 때문에 각각의 쓰레드에서 생성하라는 경고성 문구가 있었다.

 

한번 테스트를 통해 실제 Multi thread환경에서 동일한 에러가 발생하는지 확인해보자.


 

테스트

package com.wedul.batch.study;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Slf4j
public class Main {

    public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

    @SneakyThrows
    public static void main(String args[]) {
        Executor executor = Executors.newFixedThreadPool(10);
        List<CompletableFuture<Date>> futures = IntStream.range(0,10000).mapToObj(i -> CompletableFuture.supplyAsync(() -> {
            log.info("{} 실행 중...", i);
            try {
                return sdf.parse("2021-04-24");
            } catch (ParseException e) {
                log.error("", e);
                return new Date();
            }
        }, executor)).collect(Collectors.toList());

        futures.stream()
            .map(df -> {
                try {
                    return df.get();
                } catch (Exception e) {
                    log.error("", e);
                    return null;
                }
            })
            .collect(Collectors.toList())
            .forEach(System.out::println);
    }

}

쓰레드 10개를 이용해서 10000번 sdf를 접근하는 테스트 코드를 만들고 돌려봤다.

 

생각보다 더 많은 에러를 확인할 수 있었다.

java.util.concurrent.ExecutionException: java.lang.NumberFormatException: multiple points
	at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:395)
	at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1999)
	at com.wedul.batch.study.Main.lambda$main$2(Main.java:38)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
	at com.wedul.batch.study.Main.main(Main.java:44)
Caused by: java.lang.NumberFormatException: multiple points
	at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
	at java.base/jdk.internal.math.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.base/java.lang.Double.parseDouble(Double.java:543)
	at java.base/java.text.DigitList.getDouble(DigitList.java:169)
	at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2126)
	at java.base/java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1933)
	at java.base/java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1541)
	at java.base/java.text.DateFormat.parse(DateFormat.java:393)
	at com.wedul.batch.study.Main.lambda$main$0(Main.java:28)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)

 

 

SimpleDateFormat은 각자 쓰레드에서 생성해서 사용하거나 다른 라이브러리를 사용하는 것이 좋겠다.

 

참조

https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html

반응형

'JAVA' 카테고리의 다른 글

Java의 거대 정수를 담을 수 있는 BigInteger  (0) 2018.07.31