zoukankan      html  css  js  c++  java
  • SimpleDateFormat线程不安全原因及解决方案

    一、

    线程不安全验证:

    /**
     * SimpleDateFormat线程安全测试
     * 〈功能详细描述〉
     *
     * @author 17090889
     * @see [相关类/方法](可选)
     * @since [产品/模块版本] (可选)
     */
    public class SimpleDateFormatTest {
        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() {
                        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();
                        }
                    }
                });
            }
        }

    输出:

      true
      false
      true
      true
      false

    出现了false,说明线程不安全

    1、format方法

    public StringBuffer format(Date date, StringBuffer toAppendTo,
                                   FieldPosition pos)
        {
            pos.beginIndex = pos.endIndex = 0;
            return format(date, toAppendTo, pos.getFieldDelegate());
        }
    
        // Called from Format after creating a FieldDelegate
        private StringBuffer format(Date date, StringBuffer toAppendTo,
                                    FieldDelegate delegate) {
            // Convert input date to time field list
            calendar.setTime(date);
    
            boolean useDateFormatSymbols = useDateFormatSymbols();
    
            for (int i = 0; i < compiledPattern.length; ) {
                int tag = compiledPattern[i] >>> 8;
                int count = compiledPattern[i++] & 0xff;
                if (count == 255) {
                    count = compiledPattern[i++] << 16;
                    count |= compiledPattern[i++];
                }
    
                switch (tag) {
                case TAG_QUOTE_ASCII_CHAR:
                    toAppendTo.append((char)count);
                    break;
    
                case TAG_QUOTE_CHARS:
                    toAppendTo.append(compiledPattern, i, count);
                    i += count;
                    break;
    
                default:
                    subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
                    break;
                }
            }
            return toAppendTo;
        }
     protected Calendar calendar;

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

    此外,parse方法也是线程不安全的,parse方法实际调用的是CalenderBuilder的establish来进行解析,其方法中主要步骤不是原子操作。

    解决方案:

      1、将SimpleDateFormat定义成局部变量

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

      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();
                        }
                    }
                });
            }
        }
    }

      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

      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();

       2、Instant

  • 相关阅读:
    Laravel 5.7 RCE (CVE-2019-9081)
    Laravel 5.8 RCE 分析
    CVE-2018-12613 的一些思考
    2019CISCN华南线下两道web复现
    Intellij idea导入项目时没有目录结构
    [BZOJ4907]柠檬
    [BZOJ3675]序列分割
    aes加解密
    java:基于redis实现分布式定时任务
    PBKDF2加密
  • 原文地址:https://www.cnblogs.com/yangyongjie/p/11017409.html
Copyright © 2011-2022 走看看