SimpleDateFormat的线程安全

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,并且也不会有什么问题,但是发现问题->寻找解决方案->找到适合的解决方案,这种思维是我们需要去锻炼的。希望今天的分享能给大家带来帮助。

扫码关注,不迷路
扫码关注

猜你喜欢

转载自blog.csdn.net/Leon_cx/article/details/81049564