SimpleDateFormat 用于在 Java 中格式化和解析日期。
您可以使用 yyyy-MM-dd HH:mm:ss
之类的日期时间模式创建 SimpleDateFormat
的实例,然后使用该实例将日期格式化并解析为字符串。
关于 SimpleDateFormat
类需要注意的最重要的事情之一是它不是线程安全的,如果使用不当会导致多线程环境中的问题。
我写这篇文章是因为我看到开发人员在多线程环境中盲目使用 SimpleDateFormat
而不知道和处理它不是线程安全的事实。
让我们了解当我们尝试在没有任何同步的多线程环境中使用 SimpleDateFormat
时会发生什么。
下面是一个非常简单的类的例子,我们用预定义的模式解析给定的日期,但我们从多个线程并发。
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleDateFormatThreadUnsafetyExample {
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
public static void main(String[] args) {
String dateStr = "2018-06-22T10:00:00";
ExecutorService executorService = Executors.newFixedThreadPool(10);
Runnable task = new Runnable() {
@Override
public void run() {
parseDate(dateStr);
}
};
for(int i = 0; i < 100; i++) {
executorService.submit(task);
}
executorService.shutdown();
}
private static void parseDate(String dateStr) {
try {
Date date = simpleDateFormat.parse(dateStr);
System.out.println("Successfully Parsed Date " + date);
} catch (ParseException e) {
System.out.println("ParseError " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}
够简单!我们有一个 SimpleDateFormat
实例,用于解析来自多个线程的日期。
如果你运行上面的程序,它会产生这样的输出(你的输出可能与我的不同,但会有类似的错误)-
# Output
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Thu Jun 22 10:00:00 IST 2220
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:2098)
at java.base/java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1915)
at java.base/java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1529)
at java.base/java.text.DateFormat.parse(DateFormat.java:386)
at SimpleDateFormatThreadUnsafetyExample.parseDate(SimpleDateFormatThreadUnsafetyExample.java:32)
at SimpleDateFormatThreadUnsafetyExample.access$000(SimpleDateFormatThreadUnsafetyExample.java:8)
at SimpleDateFormatThreadUnsafetyExample$1.run(SimpleDateFormatThreadUnsafetyExample.java:19)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:514)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.base/java.lang.Thread.run(Thread.java:844)
java.lang.NumberFormatException: For input string: ""
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.base/java.lang.Long.parseLong(Long.java:702)
at java.base/java.lang.Long.parseLong(Long.java:817)
at java.base/java.text.DigitList.getLong(DigitList.java:195)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2093)
at java.base/java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2222)
at java.base/java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1529)
at java.base/java.text.DateFormat.parse(DateFormat.java:386)
at SimpleDateFormatThreadUnsafetyExample.parseDate(SimpleDateFormatThreadUnsafetyExample.java:32)
at SimpleDateFormatThreadUnsafetyExample.access$000(SimpleDateFormatThreadUnsafetyExample.java:8)
at SimpleDateFormatThreadUnsafetyExample$1.run(SimpleDateFormatThreadUnsafetyExample.java:19)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:514)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.base/java.lang.Thread.run(Thread.java:844)
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
## More output....... (Omitted for brevity)
SimpleDateFormat
类会改变其内部状态以格式化和解析日期。这就是为什么当多个线程同时使用 SimpleDateFormat
的同一个实例时会导致这些问题。
你有两个选择——
为每个线程创建一个新的 SimpleDateFormat
实例。
*
使用 synchronized
关键字或 lock
同步多个线程的并发访问。
SimpleDateFormat
的单独实例import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleDateFormatThreadUnsafetyExample {
public static void main(String[] args) {
String dateStr = "2018-06-22T10:00:00";
ExecutorService executorService = Executors.newFixedThreadPool(10);
Runnable task = new Runnable() {
@Override
public void run() {
parseDate(dateStr);
}
};
for(int i = 0; i < 100; i++) {
executorService.submit(task);
}
executorService.shutdown();
}
private static void parseDate(String dateStr) {
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
Date date = simpleDateFormat.parse(dateStr);
System.out.println("Successfully Parsed Date " + date);
} catch (ParseException e) {
System.out.println("ParseError " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}
请注意,我已将 SimpleDateFormat
实例创建移到 parseDate()
方法中。这样,我们为每个线程创建了一个新实例。上面程序的输出不会有任何错误 -
# Output
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
## More output....... (Omitted for brevity)
SimpleDateFormat
的同一个实例但同步并发访问import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleDateFormatThreadUnsafetyExample {
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
public static void main(String[] args) {
String dateStr = "2018-06-22T10:00:00";
ExecutorService executorService = Executors.newFixedThreadPool(10);
Runnable task = new Runnable() {
@Override
public void run() {
parseDate(dateStr);
}
};
for(int i = 0; i < 100; i++) {
executorService.submit(task);
}
executorService.shutdown();
}
private synchronized static void parseDate(String dateStr) {
try {
Date date = simpleDateFormat.parse(dateStr);
System.out.println("Successfully Parsed Date " + date);
} catch (ParseException e) {
System.out.println("ParseError " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}
在上面的例子中,我在 parseDate()
方法中添加了一个 synchronized
关键字。在这种情况下,一次只有一个线程可以进入 parseDate()
方法。
是的!不要使用 SimpleDateFormat
。 Java 8 有一个更好更增强的 DateTimeFormatter,它也是线程安全的。
您还应该避免使用 Date
和 Calendar
类,并尝试使用 Java 8 DateTime 类,如 OffsetDateTime
、ZonedDateTime
、LocalDateTime
、 LocalDate
、LocalTime
等。它们比 Date
和 Calendar
类具有更多的功能。
直到下一次…
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://www.callicoder.com/java-simpledateformat-thread-safety-issues/
内容来源于网络,如有侵权,请联系作者删除!