SimpleDateFormat非线程安全,即在多线程环境下解析字符串为日期对象,或格式化日期为字符串时,会抛出异常;当然,这是一个老生常谈的问题;
本文参考了已有 SimpleDateFormat的分析文章,做了总结,包括SimpleDateFormat报错代码,解决方法代码。
解决方法包括:
参考资料:
Java's SimpleDateFormat is not thread-safe, Use carefully in multi-threaded environments | CalliCoderThe SimpleDateFormat class in Java is not thread-safe. You should either create separate instances of SimpleDateFormat for every thread, or synchronize concurrent access by multiple threads with a synchronized keyword or a lock.https://www.callicoder.com/java-simpledateformat-thread-safety-issues/
/**
* @Description simple data format 解析失败
* @author xiao tang
* @version 1.0.0
* @createTime 2023年10月05日
*/
public class SimpleDateFormatError {
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable task = () -> parse("20231005");
// 线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
ArrayList> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
results.add(threadPool.submit(task));
}
// 获取异步执行结果
for (Future result : results) {
System.out.println(result.get());
}
threadPool.shutdown();
}
public static Date parse(String dateStr) {
try {
return SIMPLE_DATE_FORMAT.parse(dateStr);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
结果:
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:1867)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.java8.datetime.SimpleDateFormatError.parse(SimpleDateFormatError.java:34)
at com.java8.datetime.SimpleDateFormatError.lambda$main$0(SimpleDateFormatError.java:18)
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)
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:1867)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.java8.datetime.SimpleDateFormatError.parse(SimpleDateFormatError.java:34)
at com.java8.datetime.SimpleDateFormatError.lambda$main$0(SimpleDateFormatError.java:18)
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)
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:1867)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.java8.datetime.SimpleDateFormatError.parse(SimpleDateFormatError.java:34)
at com.java8.datetime.SimpleDateFormatError.lambda$main$0(SimpleDateFormatError.java:18)
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)
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:1867)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.java8.datetime.SimpleDateFormatError.parse(SimpleDateFormatError.java:34)
at com.java8.datetime.SimpleDateFormatError.lambda$main$0(SimpleDateFormatError.java:18)
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)
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:1867)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.java8.datetime.SimpleDateFormatError.parse(SimpleDateFormatError.java:34)
at com.java8.datetime.SimpleDateFormatError.lambda$main$0(SimpleDateFormatError.java:18)
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)
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:1867)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.java8.datetime.SimpleDateFormatError.parse(SimpleDateFormatError.java:34)
at com.java8.datetime.SimpleDateFormatError.lambda$main$0(SimpleDateFormatError.java:18)
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)
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:1867)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.java8.datetime.SimpleDateFormatError.parse(SimpleDateFormatError.java:34)
at com.java8.datetime.SimpleDateFormatError.lambda$main$0(SimpleDateFormatError.java:18)
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)
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:1867)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.java8.datetime.SimpleDateFormatError.parse(SimpleDateFormatError.java:34)
at com.java8.datetime.SimpleDateFormatError.lambda$main$0(SimpleDateFormatError.java:18)
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)
null
null
null
null
null
null
null
null
Thu Oct 05 00:00:00 CST 2023
Thu Oct 05 00:00:00 CST 2023
分析,显然,在多线程环境下,SimpleDateFormat解析字符串到日期对象部分失败;
原因:SimpleDateFormat在解析时,会改变其内部状态以格式化或解析日期。多线程环境下,线程1用的内部状态是线程2修改后的,存在状态不一致的问题;
本文给出的解决方法有4种:
/**
* @Description simple data format 解析失败的解决方法1-SimpleDateFormat使用单例(每次调用都新建一个实例)
* @author xiao tang
* @version 1.0.0
* @createTime 2023年10月05日
*/
public class SimpleDateFormatErrorSolutionWithInstance {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable task = () -> parse("20231005");
// 线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
ArrayList> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
results.add(threadPool.submit(task));
}
// 获取异步执行结果
for (Future result : results) {
System.out.println(result.get());
}
threadPool.shutdown();
}
public static Date parse(String dateStr) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
try {
return simpleDateFormat.parse(dateStr);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
结果:
Thu Oct 05 00:00:00 CST 2023
Thu Oct 05 00:00:00 CST 2023
Thu Oct 05 00:00:00 CST 2023
Thu Oct 05 00:00:00 CST 2023
Thu Oct 05 00:00:00 CST 2023
Thu Oct 05 00:00:00 CST 2023
Thu Oct 05 00:00:00 CST 2023
Thu Oct 05 00:00:00 CST 2023
Thu Oct 05 00:00:00 CST 2023
Thu Oct 05 00:00:00 CST 2023
Process finished with exit code 0
本文使用了 synchronized,parse方法会同步执行;没有多线程环境;
/**
* @Description simple data format 解析失败的解决方法2-加锁
* @author xiao tang
* @version 1.0.0
* @createTime 2023年10月05日
*/
public class SimpleDateFormatErrorSolutionWithLock {
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable task = () -> parse("20231005");
// 线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
ArrayList> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
results.add(threadPool.submit(task));
}
// 获取异步执行结果
for (Future result : results) {
System.out.println(result.get());
}
threadPool.shutdown();
}
public synchronized static Date parse(String dateStr) {
try {
return SIMPLE_DATE_FORMAT.parse(dateStr);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
因为ThreadLocal创建的资源属于线程级别,处理不好,容易造成内存泄漏;
/**
* @Description simple data format 解析失败的解决方法3-使用 ThreadLocal
* @author xiao tang
* @version 1.0.0
* @createTime 2023年10月05日
*/
public class SimpleDateFormatErrorSolutionWithThreadLocal {
private static final ThreadLocal THREAD_LOCAL = new ThreadLocal() {
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyyMMdd");
}
};
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable task = () -> {
Date result = parse("20231005");
// 这里要清除当前线程持有的线程级变量-SimpleDateFormat单例,否则会造成内存泄漏
THREAD_LOCAL.remove();
return result;
};
// 线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
ArrayList> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
results.add(threadPool.submit(task));
}
// 获取异步执行结果
for (Future result : results) {
System.out.println(result.get());
}
threadPool.shutdown();
}
public synchronized static Date parse(String dateStr) {
try {
return THREAD_LOCAL.get().parse(dateStr);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
/**
* @Description simple data format 解析失败的解决方法3-使用 java8 DateTimeFormatter
* @author xiao tang
* @version 1.0.0
* @createTime 2023年10月05日
*/
public class SimpleDateFormatErrorSolutionWithJava8DateTimeFormatter {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable task = () -> parse("20231005");
// 线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
ArrayList> results = new ArrayList<>();
for (int i = 0; i < 10; i++) {
results.add(threadPool.submit(task));
}
// 获取异步执行结果
for (Future result : results) {
System.out.println(result.get());
}
threadPool.shutdown();
}
public synchronized static LocalDate parse(String dateStr) {
try {
return LocalDate.parse(dateStr, DATE_TIME_FORMATTER);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
不要使用 SimpleDateFormat;java8提供了更好的和增强的 DateTimeFormatter,且是线程安全的;
也应该避免使用 Date, Calendar 类; 尝试使用 java8 DateTime 类,如 OffsetDateTime, ZoneDateTime, LocalDateTime, LocalDate, LocalTime 等;java8提供的日期时间类比 Date, Calendar 有更好的性能;