"Java Date Format is not threadsafe" (자바 날짜 포맷은 스레드 세이프가 아닙니다)는 어떤 결과를 초래합니까?
Java Date Format이 스레드 세이프가 되지 않는 것에 대해 모두가 주의를 기울이고 있으며, 저는 그 개념을 이론적으로 이해하고 있습니다.
하지만 이것 때문에 우리가 직면할 수 있는 실제적인 문제들을 시각화할 수 없습니다.예를 들어, 클래스에 DateFormat 필드가 있고 다중 스레드 환경에서 클래스의 다른 메서드(포맷 날짜)에서 동일한 필드가 사용됩니다.
원인:
- 포맷 예외와 같은 예외
- 데이터의 불일치
- 다른 문제는요?
그리고 왜 그런지 설명해 주세요.
시험 삼아 한번 해 봅시다.
에서는 여러 스레드를 을 보여 줍니다.SimpleDateFormat
.
프로그램:
public static void main(String[] args) throws Exception {
final DateFormat format = new SimpleDateFormat("yyyyMMdd");
Callable<Date> task = new Callable<Date>(){
public Date call() throws Exception {
return format.parse("20101022");
}
};
//pool with 5 threads
ExecutorService exec = Executors.newFixedThreadPool(5);
List<Future<Date>> results = new ArrayList<Future<Date>>();
//perform 10 date conversions
for(int i = 0 ; i < 10 ; i++){
results.add(exec.submit(task));
}
exec.shutdown();
//look at the results
for(Future<Date> result : results){
System.out.println(result.get());
}
}
이 작업을 몇 번 실행하면 다음과 같이 표시됩니다.
예외:
다음은 몇 가지 예입니다.
1.
Caused by: java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
at java.lang.Long.parseLong(Long.java:431)
at java.lang.Long.parseLong(Long.java:468)
at java.text.DigitList.getLong(DigitList.java:177)
at java.text.DecimalFormat.parse(DecimalFormat.java:1298)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589)
2.
Caused by: java.lang.NumberFormatException: For input string: ".10201E.102014E4"
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1224)
at java.lang.Double.parseDouble(Double.java:510)
at java.text.DigitList.getDouble(DigitList.java:151)
at java.text.DecimalFormat.parse(DecimalFormat.java:1303)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589)
3.
Caused by: java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1084)
at java.lang.Double.parseDouble(Double.java:510)
at java.text.DigitList.getDouble(DigitList.java:151)
at java.text.DecimalFormat.parse(DecimalFormat.java:1303)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1936)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1312)
잘못된 결과:
Sat Oct 22 00:00:00 BST 2011
Thu Jan 22 00:00:00 GMT 1970
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Thu Oct 22 00:00:00 GMT 1970
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
올바른 결과:
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
Fri Oct 22 00:00:00 BST 2010
또 다른 은 DateFormats를 하는 입니다.ThreadLocal
the " " " 。DateFormat
즉, 각 . 즉, 각 스레드는 자체 복사본을 가지며 다른 스레드가 릴리스할 때까지 기다릴 필요가 없습니다.음음음같 뭇매하다
public class DateFormatTest {
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyyMMdd");
}
};
public Date convert(String source) throws ParseException{
Date d = df.get().parse(source);
return d;
}
}
여기 더 자세한 내용을 담은 좋은 게시물이 있습니다.
데이터가 파손될 수 있습니다.예를 들어, 2개의 날짜를 동시에 해석하는 경우, 콜이 다른 콜의 데이터로 오염될 수 있습니다.
이것이 어떻게 일어날지 쉽게 상상할 수 있습니다.파싱은 지금까지 읽은 내용에 대해 일정량의 상태를 유지하는 것을 수반하는 경우가 많습니다.두 개의 스레드가 같은 상태를 짓밟으면 문제가 발생합니다.를 들어, 「」라고 하는 것은,DateFormat
calendar
필드 " " " " " "Calendar
「」를해 주세요SimpleDateFormat
는 , 라고 부릅니다.calendar.set(...)
다른 은 고고하 and and and and and and and and and and and라고 말합니다.calendar.get(...)
이건 확실히 안전하지 않아
정확한 이유는 아직 조사하지 않았습니다DateFormat
스레드 세이프가 아닙니다만, 동기화하지 않으면 안전하지 않다는 것을 아는 것만으로 충분합니다.안전하지 않은 것의 정확한 방법은 릴리즈 사이에 바뀔 수도 있습니다.
개인적으로 Joda Time의 파서를 사용하는 것은 스레드 세이프이기 때문입니다.Joda Time은 날짜 및 시간 API로 시작하는 것이 훨씬 좋습니다.
Java 8 을 사용하고 있는 경우는, 을 사용할 수 있습니다.
패턴으로 작성된 포메터는 필요한 횟수만큼 사용할 수 있으며, 불변하며 스레드 안전합니다.
코드:
LocalDate date = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String text = date.format(formatter);
System.out.println(text);
출력:
2017-04-17
으로 말하면, 「」는 .DateFormat
스레드에 로, "" "" "" "" " " " " " " " " " " " " " " " ", " " " " "static
.
날짜 형식이 동기화되지 않았습니다.각 스레드에 대해 별도의 형식 인스턴스를 생성하는 것이 좋습니다.
혹시 .Foo.handleBar(..)
의 스레드 대신에 .
public class Foo {
private DateFormat df = new SimpleDateFormat("dd/mm/yyyy");
public void handleBar(Bar bar) {
bar.setFormattedDate(df.format(bar.getStringDate());
}
}
다음을 사용합니다.
public class Foo {
public void handleBar(Bar bar) {
DateFormat df = new SimpleDateFormat("dd/mm/yyyy");
bar.setFormattedDate(df.format(bar.getStringDate());
}
}
어떤 '아', '아', '아', '아', '아', '아', '아', '아', '아', '아', '아', '아', '아', '아', '아', '아', '아', '아', '아', 'static
DateFormat
Sket이와 같이 외부를 모두 할 수 Jon Sket 사용).synchronized
의 DateFormat
)
날짜 형식이 동기화되지 않았습니다.각 스레드에 대해 별도의 형식 인스턴스를 생성하는 것이 좋습니다.여러 스레드가 동시에 형식에 액세스할 경우 외부에서 동기화해야 합니다.
즉, DateFormat 객체가 있고 두 개의 다른 스레드에서 동일한 객체에 액세스하고 있으며 두 스레드가 동일한 객체의 동일한 메서드에 동시에 입력되므로 적절한 결과를 얻을 수 없습니다.
DateFormat으로 작업해야 하는 경우에는 어떤 조치를 취해야 합니다.
public synchronized myFormat(){
// call here actual format method
}
가장 좋은 답변으로 도그베인은 사용 예를 들었다.parse
에서는 '체크해 주세요'를 체크해 .format
★★★★★★ 。
실행자(동시 스레드)의 수를 변경하면 다른 결과가 나타납니다.내 실험 결과:
- ★★를 남기다
newFixedThreadPool
루프는 매번 실패합니다. - 1로 설정하면 루프는 항상 동작합니다(모든 태스크가 실제로1개씩 실행되므로 분명).
- 2로 설정하면 루프가 작동할 확률은 약 6%에 불과합니다.
프로세서에 따라 YMMV가 다를 수 있습니다.
format
다른 스레드에서 시간을 포맷하여 함수가 실패합니다.은, 내부적으로는 「」이기 때문입니다.format
는 is가사사 function function function function function function를 하고 있습니다.calendar
의 format
and. 그리고 리리그그 the thecalendar
입니다.SimpleDateFormat
업업...하...
/**
* Test SimpleDateFormat.format (non) thread-safety.
*
* @throws Exception
*/
private static void testFormatterSafety() throws Exception {
final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
final Calendar calendar1 = new GregorianCalendar(2013,1,28,13,24,56);
final Calendar calendar2 = new GregorianCalendar(2014,1,28,13,24,56);
String expected[] = {"2013-02-28 13:24:56", "2014-02-28 13:24:56"};
Callable<String> task1 = new Callable<String>() {
@Override
public String call() throws Exception {
return "0#" + format.format(calendar1.getTime());
}
};
Callable<String> task2 = new Callable<String>() {
@Override
public String call() throws Exception {
return "1#" + format.format(calendar2.getTime());
}
};
//pool with X threads
// note that using more then CPU-threads will not give you a performance boost
ExecutorService exec = Executors.newFixedThreadPool(5);
List<Future<String>> results = new ArrayList<>();
//perform some date conversions
for (int i = 0; i < 1000; i++) {
results.add(exec.submit(task1));
results.add(exec.submit(task2));
}
exec.shutdown();
//look at the results
for (Future<String> result : results) {
String answer = result.get();
String[] split = answer.split("#");
Integer calendarNo = Integer.parseInt(split[0]);
String formatted = split[1];
if (!expected[calendarNo].equals(formatted)) {
System.out.println("formatted: " + formatted);
System.out.println("expected: " + expected[calendarNo]);
System.out.println("answer: " + answer);
throw new Exception("formatted != expected");
/**
} else {
System.out.println("OK answer: " + answer);
/**/
}
}
System.out.println("OK: Loop finished");
}
이치노에서 정적인 것을 .DateFormat
및 그 오브젝트라고 .format()
JDBC에 의한 것입니다.를 다른 이름으로문(SQL select)이 (SQL select 문은 다른 이름으로 읽습니다.SELECT date_from, date_from AS date_from1 ...
)의 다양한 가 사용되고 있었습니다 이러한 문장은 5개의 스레드에서 다양한 날짜에 사용되고 있습니다.WHERE
으로 보였지만 는 같은 과 요일이바뀌었을 입니다.날짜는 "보통"으로 보였지만 값은 달랐습니다. 반면 모든 날짜는 같은 해의 날짜로 월과 요일이 바뀌었을 뿐입니다.
다른 답변은 이러한 부패를 피하는 방법을 보여줍니다.나는 내 것을 만들었다고요.DateFormat
을 호출하는 입니다.static은 SQL을 호출합니다.이치노둘 다 성능에서 차이가 없이 잘 작동했습니다.
Format,, Format의 은 세이프 .형식, 날짜 형식, 메시지 형식 등이 스레드 안전성을 유지하도록 설계되지 않았습니다. 해석 은, 「」, 「」, 「」를 합니다.Calendar.clone()
메서드 및 캘린더 풋프린트에 영향을 미치기 때문에 동시에 여러 스레드를 구문 분석하면 캘린더 인스턴스의 복제가 변경됩니다.
자세한 내용은 DateFormat 스레드 안전성 문제에 대한 결과를 포함한 다음과 같은 버그 보고서입니다.
단일 DateFormat 인스턴스를 조작/액세스하는 스레드가 여러 개 있고 동기화를 사용하지 않으면 결과를 스크램블할 수 있습니다.그 이유는 여러 비원자 작업이 상태를 바꾸거나 메모리를 일관되게 볼 수 있기 때문입니다.
이것은 DateFormat이 스레드 세이프가 아님을 나타내는 간단한 코드입니다.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class DateTimeChecker {
static DateFormat df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH);
public static void main(String args[]){
String target1 = "Thu Sep 28 20:29:30 JST 2000";
String target2 = "Thu Sep 28 20:29:30 JST 2001";
String target3 = "Thu Sep 28 20:29:30 JST 2002";
runThread(target1);
runThread(target2);
runThread(target3);
}
public static void runThread(String target){
Runnable myRunnable = new Runnable(){
public void run(){
Date result = null;
try {
result = df.parse(target);
} catch (ParseException e) {
e.printStackTrace();
System.out.println("Ecxfrt");
}
System.out.println(Thread.currentThread().getName() + " " + result);
}
};
Thread thread = new Thread(myRunnable);
thread.start();
}
}
모든 스레드가 동일한 SimpleDateFormat 개체를 사용하므로 다음과 같은 예외가 발생합니다.
Exception in thread "Thread-0" Exception in thread "Thread-2" Exception in thread "Thread-1" java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)
at sun.misc.FloatingDecimal.parseDouble(Unknown Source)
at java.lang.Double.parseDouble(Unknown Source)
at java.text.DigitList.getDouble(Unknown Source)
at java.text.DecimalFormat.parse(Unknown Source)
at java.text.SimpleDateFormat.subParse(Unknown Source)
at java.text.SimpleDateFormat.parse(Unknown Source)
at java.text.DateFormat.parse(Unknown Source)
at DateTimeChecker$1.run(DateTimeChecker.java:24)
at java.lang.Thread.run(Unknown Source)
java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)
at sun.misc.FloatingDecimal.parseDouble(Unknown Source)
at java.lang.Double.parseDouble(Unknown Source)
at java.text.DigitList.getDouble(Unknown Source)
at java.text.DecimalFormat.parse(Unknown Source)
at java.text.SimpleDateFormat.subParse(Unknown Source)
at java.text.SimpleDateFormat.parse(Unknown Source)
at java.text.DateFormat.parse(Unknown Source)
at DateTimeChecker$1.run(DateTimeChecker.java:24)
at java.lang.Thread.run(Unknown Source)
java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)
at sun.misc.FloatingDecimal.parseDouble(Unknown Source)
at java.lang.Double.parseDouble(Unknown Source)
at java.text.DigitList.getDouble(Unknown Source)
at java.text.DecimalFormat.parse(Unknown Source)
at java.text.SimpleDateFormat.subParse(Unknown Source)
at java.text.SimpleDateFormat.parse(Unknown Source)
at java.text.DateFormat.parse(Unknown Source)
at DateTimeChecker$1.run(DateTimeChecker.java:24)
at java.lang.Thread.run(Unknown Source)
그러나 다른 오브젝트를 다른 스레드에 전달하면 코드는 오류 없이 실행됩니다.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class DateTimeChecker {
static DateFormat df;
public static void main(String args[]){
String target1 = "Thu Sep 28 20:29:30 JST 2000";
String target2 = "Thu Sep 28 20:29:30 JST 2001";
String target3 = "Thu Sep 28 20:29:30 JST 2002";
df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH);
runThread(target1, df);
df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH);
runThread(target2, df);
df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH);
runThread(target3, df);
}
public static void runThread(String target, DateFormat df){
Runnable myRunnable = new Runnable(){
public void run(){
Date result = null;
try {
result = df.parse(target);
} catch (ParseException e) {
e.printStackTrace();
System.out.println("Ecxfrt");
}
System.out.println(Thread.currentThread().getName() + " " + result);
}
};
Thread thread = new Thread(myRunnable);
thread.start();
}
}
결과는 다음과 같습니다.
Thread-0 Thu Sep 28 17:29:30 IST 2000
Thread-2 Sat Sep 28 17:29:30 IST 2002
Thread-1 Fri Sep 28 17:29:30 IST 2001
로 인해 「」가 합니다.ArrayIndexOutOfBoundsException
잘못된 결과는 차치하고, 때때로 당신에게 크래쉬를 줄 것입니다.사용하시는 기기의 속도에 따라 다릅니다.노트북에서는 평균 100,000건 중 한 번꼴로 발생합니다.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<?> future1 = executorService.submit(() -> {
for (int i = 0; i < 99000; i++) {
sdf.format(Date.from(LocalDate.parse("2019-12-31").atStartOfDay().toInstant(UTC)));
}
});
executorService.submit(() -> {
for (int i = 0; i < 99000; i++) {
sdf.format(Date.from(LocalDate.parse("2020-04-17").atStartOfDay().toInstant(UTC)));
}
});
future1.get();
마지막 행은 연기된 실행자 예외를 트리거합니다.
java.lang.ArrayIndexOutOfBoundsException: Index 16 out of bounds for length 13
at java.base/sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:453)
at java.base/java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2394)
at java.base/java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2309)
at java.base/java.util.Calendar.complete(Calendar.java:2301)
at java.base/java.util.Calendar.get(Calendar.java:1856)
at java.base/java.text.SimpleDateFormat.subFormat(SimpleDateFormat.java:1150)
at java.base/java.text.SimpleDateFormat.format(SimpleDateFormat.java:997)
at java.base/java.text.SimpleDateFormat.format(SimpleDateFormat.java:967)
at java.base/java.text.DateFormat.format(DateFormat.java:374)
언급URL : https://stackoverflow.com/questions/4021151/java-dateformat-is-not-threadsafe-what-does-this-leads-to
'sourcecode' 카테고리의 다른 글
한 Vuejs 구성 요소에서 다른 구성 요소로 OAuth 토큰 전달 (0) | 2022.07.31 |
---|---|
오래된 데스크톱 컴퓨터에 소형 운영체제를 구축하려면 어떻게 해야 하나요? (0) | 2022.07.31 |
'pow' 및 'floor'에 대한 참조가 정의되지 않았습니다. (0) | 2022.07.31 |
아이디어: javac: 소스 릴리스 1.7에는 타깃 릴리스 1.7이 필요합니다. (0) | 2022.07.31 |
Java의 +=, -=, *=, /= 복합 할당 연산자가 캐스팅을 요구하지 않는 이유는 무엇입니까? (0) | 2022.07.31 |