一、SimpleDateFormat线程不安全验证

  1. /**
  2. * SimpleDateFormat线程安全测试
  3. * 〈功能详细描述〉
  4. *
  5. * @author 17090889
  6. * @see [相关类/方法](可选)
  7. * @since [产品/模块版本] (可选)
  8. */
  9. public class SimpleDateFormatTest {
  10. private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  11. ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000), new MyThreadFactory("SimpleDateFormatTest"));
  12. public void test() {
  13. while (true) {
  14. poolExecutor.execute(new Runnable() {
  15. @Override
  16. public void run() {
  17. String dateString = simpleDateFormat.format(new Date());
  18. try {
  19. Date parseDate = simpleDateFormat.parse(dateString);
  20. String dateString2 = simpleDateFormat.format(parseDate);
  21. System.out.println(dateString.equals(dateString2));
  22. } catch (ParseException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. });
  27. }
  28. }

二、不安全原因(源码解析)

  1. public StringBuffer format(Date date, StringBuffer toAppendTo,
  2. FieldPosition pos)
  3. {
  4. pos.beginIndex = pos.endIndex = 0;
  5. return format(date, toAppendTo, pos.getFieldDelegate());
  6. }
  7. // Called from Format after creating a FieldDelegate
  8. private StringBuffer format(Date date, StringBuffer toAppendTo,
  9. FieldDelegate delegate) {
  10. // Convert input date to time field list
  11. calendar.setTime(date);
  12. boolean useDateFormatSymbols = useDateFormatSymbols();
  13. for (int i = 0; i < compiledPattern.length; ) {
  14. int tag = compiledPattern[i] >>> 8;
  15. int count = compiledPattern[i++] & 0xff;
  16. if (count == 255) {
  17. count = compiledPattern[i++] << 16;
  18. count |= compiledPattern[i++];
  19. }
  20. switch (tag) {
  21. case TAG_QUOTE_ASCII_CHAR:
  22. toAppendTo.append((char)count);
  23. break;
  24. case TAG_QUOTE_CHARS:
  25. toAppendTo.append(compiledPattern, i, count);
  26. i += count;
  27. break;
  28. default:
  29. subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
  30. break;
  31. }
  32. }
  33. return toAppendTo;
  34. }

可以看到,多个线程之间共享变量calendar,并修改calendar。因此在多线程环境下,当多个线程同时使用相同的SimpleDateFormat对象(如static修饰)的话,如调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。
此外,parse方法也是线程不安全的,parse方法实际调用的是CalenderBuilder的establish来进行解析,其方法中主要步骤不是原子操作。

三、解决方案:

3.1、将SimpleDateFormat定义成局部变量
3.2、 加一把线程同步锁:synchronized(lock)
3.3、使用ThreadLocal,每个线程都拥有自己的SimpleDateFormat对象副本。如:

/**
 * SimpleDateFormat线程安全测试
 * 〈功能详细描述〉
 *
 * @author 17090889
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
public class SimpleDateFormatTest {
        private static final ThreadLocal<SimpleDateFormat> THREAD_LOCAL = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
    //    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000), new MyThreadFactory("SimpleDateFormatTest"));

    public void test() {
        while (true) {
            poolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    SimpleDateFormat simpleDateFormat = THREAD_LOCAL.get();
                    if (simpleDateFormat == null) {
                        simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    }
                    String dateString = simpleDateFormat.format(new Date());
                    try {
                        Date parseDate = simpleDateFormat.parse(dateString);
                        String dateString2 = simpleDateFormat.format(parseDate);
                        System.out.println(dateString.equals(dateString2));
                    } catch (ParseException e) {
                        e.printStackTrace();
                    } finally {
                        local.remove();
                    }
                }
            });
        }
    }
}

3.4、使用DateTimeFormatter代替SimpleDateFormat
  DateTimeFormatter是线程安全的,默认提供了很多格式化方法,也可以通过ofPattern方法创建自定义格式化方法。
1、格式化日期示例:

 LocalDateTime localDateTime = LocalDateTime.now();
 System.out.println(localDateTime); // 2019-11-20T15:04:29.017
 DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
 String strDate=localDateTime.format(dtf);
 System.out.println(strDate); // 2019/23/20 15:23:46

2、解析日期

DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
 LocalDateTime localDateTime=LocalDateTime.parse("2019/11/20 15:23:46",dtf);
 System.out.println(localDateTime); // 2019-11-20T15:23:46

二、使用JDK8全新的日期和时间API

2.1、LocalDate、LocalTime、LocalDateTime
Date如果不格式化,打印出的日期可读性极差,可使用LocalDateTime代替。
1、LocalDateTime:获取年月日时分秒等于LocalDate+LocalTime

LocalDateTime localDateTime = LocalDateTime.now();
 System.out.println(localDateTime); // 2019-11-20T15:04:29.017
 LocalDate localDate = localDateTime.toLocalDate();
 LocalTime localTime = localDateTime.toLocalTime();

2、LocalDate:获取年月日

LocalDate localDate=LocalDate.now();
 System.out.println(localDate); // 2019-11-20

3、LocalTime:获取时分秒

 LocalTime localTime = LocalTime.now();
 System.out.println(localTime); // 15:14:17.081
 int hour = localTime.getHour();
 int minute = localTime.getMinute();
 int second = localTime.getSecond();

注:参考资料

https://www.cnblogs.com/yangyongjie/p/11017409.html