SimpleDateFormat 线程安全问题

简介: SimpleDateFormat 线程安全问题

为啥线程不安全?

主要包含两大块 parse 和 format  不安全。

可以看到,多个线程之间共享变量calendar,并修改calendar。因此在多线程环境下,当多个线程同时使用相同的SimpleDateFormat对象(如static修饰)的话,如调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。


SimpleDateFormat 的 format 方法线程不安全问题


public final String format(Date arg0) {
        return this.format(arg0, new StringBuffer(),
                DontCareFieldPosition.INSTANCE).toString();
    }


this.format 使用的是 SimpleDateFormat 的format 方法

private StringBuffer format(Date arg0, StringBuffer arg1, FieldDelegate arg2) {
        this.calendar.setTime(arg0);
        boolean arg3 = this.useDateFormatSymbols();
        int arg4 = 0;
        while (arg4 < this.compiledPattern.length) {
            int arg5 = this.compiledPattern[arg4] >>> 8;
            int arg6 = this.compiledPattern[arg4++] & 255;
            if (arg6 == 255) {
                arg6 = this.compiledPattern[arg4++] << 16;
                arg6 |= this.compiledPattern[arg4++];
            }
            switch (arg5) {
                case 100 :
                    arg1.append((char) arg6);
                    break;
                case 101 :
                    arg1.append(this.compiledPattern, arg4, arg6);
                    arg4 += arg6;
                    break;
                default :
                    this.subFormat(arg5, arg6, arg2, arg1, arg3);
            }
        }
        return arg1;
    }


不同线程

this.calendar.setTime(arg0);

依然会导致线程不安全问题。


SimpleDateFormat是继承DateFormat类,DateFormat类中维护一个Calendar 对象

SimpleDateFormat 继承 DateFormat ,使用的calendar 是父类 DateFormat中的
public class SimpleDateFormat extends DateFormat {}
DateFormat 的 calendar 被用来进行 日期-时间计算,也被用于 format 方法也被用于 parse方法
public abstract class DateFormat extends Format {
    protected Calendar calendar;
}


Parse 导致的线程安全问题

SimpleDateFormat 的 parse 方法


public Date parse(String arg0, ParsePosition arg1) {
        this.checkNegativeNumberExpression();
        int arg2 = arg1.index;
        int arg3 = arg2;
        int arg4 = arg0.length();
        boolean[] arg5 = new boolean[]{false};
        CalendarBuilder arg6 = new CalendarBuilder();
        int arg7 = 0;
        label82 : while (arg7 < this.compiledPattern.length) {
            int arg8 = this.compiledPattern[arg7] >>> 8;
            int arg9 = this.compiledPattern[arg7++] & 255;
            if (arg9 == 255) {
                arg9 = this.compiledPattern[arg7++] << 16;
                arg9 |= this.compiledPattern[arg7++];
            }
            switch (arg8) {
                case 100 :
                    if (arg2 < arg4 && arg0.charAt(arg2) == (char) arg9) {
                        ++arg2;
                        break;
                    }
                    arg1.index = arg3;
                    arg1.errorIndex = arg2;
                    return null;
                case 101 :
                    while (true) {
                        if (arg9-- <= 0) {
                            continue label82;
                        }
                        if (arg2 >= arg4
                                || arg0.charAt(arg2) != this.compiledPattern[arg7++]) {
                            arg1.index = arg3;
                            arg1.errorIndex = arg2;
                            return null;
                        }
                        ++arg2;
                    }
                default :
                    boolean arg10 = false;
                    boolean arg11 = false;
                    if (arg7 < this.compiledPattern.length) {
                        int arg12 = this.compiledPattern[arg7] >>> 8;
                        if (arg12 != 100 && arg12 != 101) {
                            arg10 = true;
                        }
                        if (this.hasFollowingMinusSign
                                && (arg12 == 100 || arg12 == 101)) {
                            int arg13;
                            if (arg12 == 100) {
                                arg13 = this.compiledPattern[arg7] & 255;
                            } else {
                                arg13 = this.compiledPattern[arg7 + 1];
                            }
                            if (arg13 == this.minusSign) {
                                arg11 = true;
                            }
                        }
                    }
                    arg2 = this.subParse(arg0, arg2, arg8, arg9, arg10, arg5,
                            arg1, arg11, arg6);
                    if (arg2 < 0) {
                        arg1.index = arg3;
                        return null;
                    }
            }
        }
        arg1.index = arg2;
        try {
            Date arg15 = arg6.establish(this.calendar).getTime();
            if (arg5[0] && arg15.before(this.defaultCenturyStart)) {
                arg15 = arg6.addYear(100).establish(this.calendar).getTime();
            }
            return arg15;
        } catch (IllegalArgumentException arg14) {
            arg1.errorIndex = arg2;
            arg1.index = arg3;
            return null;
        }
    }


关键看

Date arg15 = arg6.establish(this.calendar).getTime();

这个里面 的 establish 方法。establish 是 CalendarBuilder 的方法


Calendar establish(Calendar arg0) {
        boolean arg1 = this.isSet(17) && this.field[17] > this.field[1];
        if (arg1 && !arg0.isWeekDateSupported()) {
            if (!this.isSet(1)) {
                this.set(1, this.field[35]);
            }
            arg1 = false;
        }
        arg0.clear();
        int arg2;
        int arg3;
        for (arg2 = 2; arg2 < this.nextStamp; ++arg2) {
            for (arg3 = 0; arg3 <= this.maxFieldIndex; ++arg3) {
                if (this.field[arg3] == arg2) {
                    arg0.set(arg3, this.field[18 + arg3]);
                    break;
                }
            }
        }
        if (arg1) {
            arg2 = this.isSet(3) ? this.field[21] : 1;
            arg3 = this.isSet(7) ? this.field[25] : arg0.getFirstDayOfWeek();
            if (!isValidDayOfWeek(arg3) && arg0.isLenient()) {
                if (arg3 >= 8) {
                    --arg3;
                    arg2 += arg3 / 7;
                    arg3 = arg3 % 7 + 1;
                } else {
                    while (arg3 <= 0) {
                        arg3 += 7;
                        --arg2;
                    }
                }
                arg3 = toCalendarDayOfWeek(arg3);
            }
            arg0.setWeekDate(this.field[35], arg2, arg3);
        }
        return arg0;
    }


主要看

arg0.clear();


这个会将 calendar 清除掉,并且没有设置新值

可知SimpleDateFormat维护的用于format和parse方法计算日期-时间的calendar被清空了,如果此时线程A将calendar清空且没有来得及设置新值,线程B也进入parse方法用到了SimpleDateFormat对象中的calendar对象,此时就会产生线程安全问题!

640.png


解决方案:


1、将SimpleDateFormat定义成局部变量

2、 加一把线程同步锁:synchronized(lock)

3、使用ThreadLocal,每个线程都拥有自己的SimpleDateFormat对象副本


解决办法栗子:threadLocal

class ThreadLocalSimpleFormatDateUtil {
    private static final String date_format = "yyyy-MM-dd HH:mm:ss";
    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>();
    public static DateFormat getDateFormat() {
        DateFormat df = threadLocal.get();
        if (df == null) {
            df = new SimpleDateFormat(date_format);
            threadLocal.set(df);
        }
        return df;
    }
    public static String formatDate(Date date) throws ParseException {
        return getDateFormat().format(date);
    }
    public static Date parse(String strDate) throws ParseException {
        return getDateFormat().parse(strDate);
    }
}


相关文章
|
6月前
|
安全 Java 开发者
丢失的8小时去哪里了?SimpleDateFormat线程不安全,多线程初始化异常解决方案
丢失的8小时去哪里了?SimpleDateFormat线程不安全,多线程初始化异常解决方案
95 0
|
存储 安全 Java
解密SimpleDateFormat类的线程安全问题和六种解决方案!
提起SimpleDateFormat类,想必做过Java开发的童鞋都不会感到陌生。没错,它就是Java中提供的日期时间的转化类。这里,为什么说SimpleDateFormat类有线程安全问题呢?有些小伙伴可能会提出疑问:我们生产环境上一直在使用SimpleDateFormat类来解析和格式化日期和时间类型的数据,一直都没有问题啊!接下来,我们就一起看下在高并发下SimpleDateFormat类为何会出现安全问题,以及如何解决SimpleDateFormat类的安全问题。
1753 1
解密SimpleDateFormat类的线程安全问题和六种解决方案!
|
安全 Java API
Java Review - SimpleDateFormat线程不安全原因的源码分析及解决办法
Java Review - SimpleDateFormat线程不安全原因的源码分析及解决办法
202 0
|
存储 安全
解决SimpleDateFormat线程安全问题
SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须通过加锁等方式保证线程安全。
347 0
|
存储 安全
解决SimpleDateFormat线程安全问题NumberFormatException: multiple points
解决SimpleDateFormat线程安全问题NumberFormatException: multiple points
104 0
|
安全
浅谈 SimpleDateFormat,第三方库joda-time,JDK8提供时间类 之性能和线程安全
浅谈 SimpleDateFormat,第三方库joda-time,JDK8提供时间类 之性能和线程安全
浅谈 SimpleDateFormat,第三方库joda-time,JDK8提供时间类 之性能和线程安全
|
缓存 安全 Java
java的SimpleDateFormat线程不安全出问题了,虚竹教你多种解决方案
java的SimpleDateFormat线程不安全出问题了,虚竹教你多种解决方案
235 0
java的SimpleDateFormat线程不安全出问题了,虚竹教你多种解决方案
|
缓存 安全 Java
java的SimpleDateFormat线程不安全出问题了,项目不支持java8,虚竹教你这一招
java的SimpleDateFormat线程不安全出问题了,项目不支持java8,虚竹教你这一招
172 0
java的SimpleDateFormat线程不安全出问题了,项目不支持java8,虚竹教你这一招
SimpleDateFormat 为什么不是线程安全的?
大家都说 SimpleDateFormat 不是线程安全的,到底哪里不安全呢? 来看 SimpleDateFormat 类的源码注释:
132 0
SimpleDateFormat 为什么不是线程安全的?