首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

一次 SimpleDateFormat 引发的惨案(3)

一次 SimpleDateFormat 引发的惨案(3)

分析

SimpleDateFormat 是 Java 中一个相当常用的类,该类用于对日期字符串进行解析和格式化,但如果使用不当会导致非常微妙和难以调试的问题,因为它不是线程安全的,在多线程环境下调用 format() 和 parse() 方法很容易产生问题。就像上面我一旦使用 JDK8 的 parallelStream() 来遍历,它就不好使了。

“知其然,必知其所以然” 。我们来分析一下为什么会输出奇怪的“穿越”日期。

我们打开 Dash 来查阅一下 JDK 文档 对于 SimpleDateFormat 的描述:

下面通过源码来看看为什么 SimpleDateFormat 和 DateFormat 类不是线程安全的真正原因:

SimpleDateFormat 继承自 DateFormat,在 DateFormat 中定义了一个 protected 属性的 Calendar 类对象:calendar。因为 Calendar 类牵扯到了时区与本地化,JDK 的实现中使用了成员变量来传递参数,这就造成在多线程的时候会出现错误。

在 format() 方法里,有这样一段代码:

    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.setTime(date) 这条语句改变了 calendar ,然后,calendar 还在 subFormat() 方法里被用到,而这就是引发问题的根源。想象一下,在一个多线程环境下,有两个线程持有了同一个SimpleDateFormat 的实例,分别调用format方法:

    线程1调用 format 方法,改变了 calendar 这个字段。
    中断来了。
    线程2开始执行,它也改变了 calendar。
    又中断了。
    线程1回来了,此时,calendar 已然不是它所设的值,而是走上了线程2设计的道路。如果多个线程同时争抢 calendar 对象,则会出现各种问题。比如时间不对,线程挂死等等。

分析一下 format() 的实现,我们不难发现,用到成员变量 calendar,唯一的好处,就是在调用 subFormat() 时,少了一个参数,却带来了这许多的问题。其实,只要在这里用一个局部变量,一路传递下去,所有问题都将迎刃而解。
返回列表