项目中,当多线程处理Date与String互相转换的时候,会出现ParseException转换异常。接着百度了一下,记录这个问题。
先贴一下原来的代码
处理的工具类 DateUtil
public class DateUtil {
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm::ss");
static String formatDate(Date date) throws ParseException{
return simpleDateFormat.format(date);
}
static Date parse(String dateStr){
try {
return simpleDateFormat.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
创建20个线程,在子线程中调用parse方法。
public class Main {
public static void main(String[] args) {
for(int i = 0 ; i < 20 ; i ++){
new Thread(() -> System.out.println(DateUtil.parse("2019-08-20 15:12:00"))).start();
}
}
}
这时候就能看到控制台输出出现了异常
输出的时间是错误的,并且抛出了多个异常。
那么,为什么呢?
来看看源码
SimpleDateFormat 继承自 DateFormat
在DateFormat中,有protected属性的Calendar,Calendar 处理的时候,牵扯到时区、本地化等问题,所以JDK中使用了成员变量来传递参数。bingo! 正是由于这个Calendar,导致了SimpleDateFormat线程不安全。
那么是不是可以在使用的时候再创建SimpleDateFormat对象?
可以是可以,将SimpleDateFormat局部私有可以解决上面的问题,但是创建多个对象会占用大量内存和JVM空间。程序员要优雅,应该还有更好的解决方案。
又是一通百度,终于找到了一篇博客。
里面介绍了一种方法,使用 ThreadLocal 关于ThreadLocal点击这个连接
根据文章,重新编写一个测试类 NewDateUtil
public class NewDateUtil {
private static ThreadLocal<DateFormat> threadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static Date parse(String dateStr){
try {
return threadLocal.get().parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
public static String format(Date date){
return threadLocal.get().format(date);
}
}
依然在main方法中调用
public class Main {
public static void main(String[] args) {
for(int i = 0 ; i < 20 ; i ++){
new Thread(() -> System.out.println(NewDateUtil.parse("2019-08-20 15:12:00"))).start();
}
}
}
此时,可以输出正确的时间
另外,文章还提到了Joda-Time 。 Joda-Time的使用方法
Joda-Time 可以将ISO8601格式的String(e.g. R2/2015-06-04T19:25:16.828696-07:00/P1DT10S)转成Date ,但是对"yyyy-MM-dd HH:mm:ss"格式没有相应的方法(找到了再更新),
一般,SimpleDateFormat 局部私有可以解决线程安全的问题对性能影响不大,我自己倾向于使用ThreadLocal来解决
以上