SimpleDateFormat的线程安全问题

记录一次SimpleDateFormat的线程安全问题;

 

问题的出现是我定义了一个静态的 :

private final static SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
然后在使用的时候是线程池调用:具体代码我就不贴了,写一个小的模拟案例来大概是这样的:

 

public class Test {
    // 定义静态的SimpleDateFormat
    private final static SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    // 定义一个线程池
    ExecutorService executorService = new ThreadPoolExecutor(0, 50,
            60L, TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>());

    public void testSimpleDate() {
        // 待转换的字符集
        List<String> dateStrList = new ArrayList<>();
        dateStrList.add("2018-04-01 10:00:01");
        dateStrList.add("2018-04-02 11:00:02");
        dateStrList.add("2018-04-03 12:00:03");
        dateStrList.add("2018-04-04 13:00:04");
        dateStrList.add("2018-04-05 14:00:05");

        // 模拟线程
        dateStrList.forEach((dateStr)->{
            executorService.execute(()->{
                try {
                    // 执行具体的parse方法
                    SDF.parse(dateStr);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            });
        });
    }
    
    public static void main(String[] args) {
        new Test().testSimpleDate();
    }
}

运行报如下错误:

Exception in thread "pool-1-thread-3" java.lang.NumberFormatException: empty String
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
	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:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at com.ant.test.Test.lambda$null$0(Test.java:45)
	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)

这里我们发现在执行parse()方法的时候,程序报错了java.lang.NumberFormatException,按理说,我一个静态的类,怎么会导致线程安全问题呢? 那么是什么原因导致这个问题:那么我们跟到parse()方法里去,看到底发生了什么?

 

/**
     * Parses text from the beginning of the given string to produce a date.
     * The method may not use the entire text of the given string.
     * <p>
     * See the {@link #parse(String, ParsePosition)} method for more information
     * on date parsing.
     *
     * @param source A <code>String</code> whose beginning should be parsed.
     * @return A <code>Date</code> parsed from the string.
     * @exception ParseException if the beginning of the specified string
     *            cannot be parsed.
     */
    public Date parse(String source) throws ParseException
    {
        ParsePosition pos = new ParsePosition(0);
        Date result = parse(source, pos);
        if (pos.index == 0)
            throw new ParseException("Unparseable date: \"" + source + "\"" ,
                pos.errorIndex);
        return result;
    }

看这行代码好像也没问题,但是我们看DateFormat这个类结构:

public abstract class DateFormat extends Format {

    /**
     * The {@link Calendar} instance used for calculating the date-time fields
     * and the instant of time. This field is used for both formatting and
     * parsing.
     *
     * <p>Subclasses should initialize this field to a {@link Calendar}
     * appropriate for the {@link Locale} associated with this
     * <code>DateFormat</code>.
     * @serial
     */
    protected Calendar calendar;

发现类里面除了继承Format类外,还有一个Calendar类属性,那么问题是不是就出在这个Calendar中呢?,我们跟到parse()方法的Date result = parse(source, pos);里面去看一下:

    Date parsedDate;
        try {
            parsedDate = calb.establish(calendar).getTime();
            // If the year value is ambiguous,
            // then the two-digit year == the default start year
            if (ambiguousYear[0]) {
                if (parsedDate.before(defaultCenturyStart)) {
                    parsedDate = calb.addYear(100).establish(calendar).getTime();
                }
            }
        }

parsedDate = calb.establish(calendar).getTime();然后再看这个方法调用:

Calendar establish(Calendar cal) {
        boolean weekDate = isSet(WEEK_YEAR)
                            && field[WEEK_YEAR] > field[YEAR];
        if (weekDate && !cal.isWeekDateSupported()) {
            // Use YEAR instead
            if (!isSet(YEAR)) {
                set(YEAR, field[MAX_FIELD + WEEK_YEAR]);
            }
            weekDate = false;
        }

        cal.clear();
        // Set the fields from the min stamp to the max stamp so that
        // the field resolution works in the Calendar.
        for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
            for (int index = 0; index <= maxFieldIndex; index++) {
                if (field[index] == stamp) {
                    cal.set(index, field[MAX_FIELD + index]);
                    break;
                }
            }
        }

在这里有一个cal.clear();它这里把我们传进来的calender给clear了,然后后面又给他重新复制返回出去;所以当一个线程执行clear,这是另一个线程也进来,那么这个calender已经被清空了,所以下一个线程进来再用calender时就会发生错误了;

这就是SimpleDateFormate的线程安全问题,后面注意就可以了:

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值