SimpleDateFormat是我们格式化时间格式时常用到的一个类,通常情况下我们需要使用到它时,都会new一个新的对象出来,所以不会遇到多线程场景下的线程安全问题。
下面带大家一起了解一下为什么SimpleDateFormat时线程不安全的,以及解决办法。
当然如果你说我就每次使用到它的时候new一个新的对象出来不就行了吗,那么我只能说,兄dei,我也是这么干的。
但是如果你有空的话也可以跟着来看看这篇文章,毕竟写文章也挺花时间的。
使用SimpleDateFormat类常用到的一个方法就是format方法,我们跟进源码看一下
private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);
...
...
}
calendar.setTime(date)这一行,根据传进来的参数date修改了calendar参数的值,那么我们跟进去看一下calendar对象的定义,在源码中看到这样一行
protected Calendar calendar;
我们可以看到calendar参数是SimpleDateFormat类的一个成员变量,那么如果多个线程同时调用了同一个SimpleDateFormat对象的format方法,线程A中的calendar变量会被线程B的format方法所修改,这就出现问题了。
既然多线程场景下会出现这样的问题,那么这边找出几种解决办法
1.需要的时候创建新实例
这就是上面说到的每次使用到SimpleDateFormat对象的时候new一个新的对象,这样每个SimpleDateFormat对象都是线程独有的对象,就不存在线程不安全的问题了。
public class DateUtil {
public static String formatDate(Date date)throws ParseException{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
public static Date parse(String strDate) throws ParseException{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.parse(strDate);
}
}
2.同步SimpleDateFormat对象的方法
我们还是使用同一个SimpleDateFormat对象,然后对会遇到多线程问题的format和parse方法加上锁
public class DateSyncUtil {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String formatDate(Date date)throws ParseException{
synchronized(sdf){
return sdf.format(date);
}
}
public static Date parse(String strDate) throws ParseException{
synchronized(sdf){
return sdf.parse(strDate);
}
}
}
3.使用ThreadLocal
使用ThreadLocal, 也是将共享变量变为独享
public class ConcurrentDateUtil {
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static Date parse(String dateStr) throws ParseException {
return threadLocal.get().parse(dateStr);
}
public static String format(Date date) {
return threadLocal.get().format(date);
}
}
下面讲述一下上述三种方法的优缺点:
方法一每次需要格式化时间的时候都要创建一个新的SimpleDateFormat对象,在系统中需要经常格式化时间的场景下会牺牲一些内存;
方法二全局只会创建一个SimpleDateFormat对象,节省了内存,但是在系统中需要经常格式化场景下,所有的线程都会去抢占这个锁,会牺牲一些时间;
方法三表面上看起来是最优的一种解决方案,每个线程独享一个SimpleDateFormat对象,不存在多线程修改calendar变量的问题,但是在系统中很少出现格式化时间的场景下,那么每个线程变量的内部线程副本中都会创建一个SimpleDateFormat对象,也会产生一些不必要的内存上的消耗。
总结一下,很多时候,一个问题会有很多种解决办法,但没有哪一种方法是最优的(如果有一种方法永远是最优的,那么其他方法就不会再出现了),我们经常需要结合不同的场景来选择具体的解决方案。
虽然今天这个问题大多数人都会选择方案1,并且也不会有什么问题,但是发现问题->寻找解决方案->找到适合的解决方案,这种思维是我们需要去锻炼的。希望今天的分享能给大家带来帮助。
扫码关注,不迷路