반응형
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에 있었다.
문서 내용을 확인해보면 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 |
---|