• SimpleDateFormat一定是线程不安全吗?


    今天一位优秀的架构师告诉我,下面这段代码SimpleDateFormat是线程不安全的。

        /**
         * 将Date按格式转化成String
         *
         * @param date  Date对象
         * @param pattern 日期类型
         * @return String
         */
        public static String date2String(Date date, String pattern) {
            if (date == null || pattern == null) {
                return null;
            }
            return new SimpleDateFormat(pattern).format(date);
        }

    那么let us test!

    简单介绍下我的测试方法

    1.时间转字符串

    2.字符串转时间

    3.时间转字符串

    比较第一个字符串和第二个字符是否相同。如果没有并发问题,那么第一个字符串跟第二个字符串肯定完全一样

    第一种情况

    一个SimpleDateFormat实例,并发执行。

    private static void newSimpleDate() {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES,
                new LinkedBlockingQueue<>(1000));
            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);
                            if (!dateString.equals(dateString2)) {
                                System.out.println(dateString.equals(dateString2));
                            }
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }

    结果:

    false
    false
    false
    false
    false
    false
    false

    说明存在线程不安全的问题。

    第二种情况

    每次新建一个SimpleDateFormat 对象

        private static void oneSimpleDate() {
            ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES,
                new LinkedBlockingQueue<>(1000));
            while (true) {
                poolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            date2String(new Date(), "yyyy-MM-dd HH:mm:ss");
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }
        public static void date2String(Date date, String pattern) throws ParseException {
    
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
    
            String dateString = new SimpleDateFormat(pattern).format(date);
            Date parseDate = simpleDateFormat.parse(dateString);
            String dateString2 = simpleDateFormat.format(parseDate);
         System.out.println(dateString.equals(dateString2));
        }

    结果:永远为true

    true
    true
    true
    true
    true
    true
    true

    说明没有线程安全问题。

    奇怪,那么SimpleDateFormat究竟有没有问题呢?

    简单看一下SimpleDateFormat.format()方法。

    
    
    protected Calendar calendar;

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

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

    SimpleDateFormat不是线程安全的,但这并不代表,它无法被线程安全的使用,当你把它作为局部变量,每次新建一个实例,或者加锁,或者采用架构师说的DateTimeFormatter都能规避这个问题。

    就像hashmap,大家都知道他是线程不安全的,在jdk1.7采用头插法时,并发会出现死循环。但是你每次new hashmap对象,去put肯定不会有问题,尽管不会有人这么用,业务也不允许。

    所以,这件事告诉我们,不要守着自己的教条主义,看到线程不安全的类,就觉得方法是线程不安全的,要看具体的使用场景;当然线程安全的类,也有可能是线程不安全的;架构师也不例外喔!

  • 相关阅读:
    Activity相关
    关于JNI接口封装(将so接口调用封装到jar包)
    在android系统源码目录下编译apk
    【原创】分享一个分析函数统计案例
    【原创】Oracle函数中对于NO_DATA_FOUND异常处理的研究
    【原创】一种维护型项目升级打包的解决方案
    【原创】如何找到Oracle中哪条记录被锁
    【原创】ORA-04068: 已丢弃程序包 的当前状态研究
    【原创】CQ数据库损坏修复
    【原创】物化视图日志对性能的影响测试
  • 原文地址:https://www.cnblogs.com/xwzp/p/14685452.html
Copyright © 2020-2023  润新知