前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SimpleDateFormat多线程下的安全性问题

SimpleDateFormat多线程下的安全性问题

作者头像
名字是乱打的
发布2021-12-24 08:51:14
5160
发布2021-12-24 08:51:14
举报
文章被收录于专栏:软件工程

背景: 最近又看到乱用SimpleDateFormat的情况,这里做个关于SimpleDateFormat多线程下的安全性问题的总结. 之前部门集合了一个时间工具类供大家使用,里面各式各样时间格式化的方法有几十上百个样子,然后由于很多方法都用的一个SimpleDateFormat,部门的机灵鬼发现这他娘不是重复代码嘛?然后就把他提出来了,提出来后后面也没发现什么问题,直到很久以后部门来了一个大流量的爬虫任务需要并发处理task,然后频繁调用时间格式化工具,然后在用这个SimpleDateFormat时候终于出现了问题,很多时间生成错乱,甚至根本不是一个时间的样子,或者直接报错了.

1.问题复现

1.1模拟并发使用SimpleDateFormat
代码语言:javascript
复制
public class TimeConcurrErrorTest {
    static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        for (int i = 0; i <100 ; ++i) {
            Thread thread = new Thread(()-> {
                    try {
                        System.out.println(sdf.parse("2020-12-11 11:17:27"));
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
            });
            thread.start();
        }
    }
}
1.2问题浮现

image

结果集如上图所示,部分时间格式转换没有出现报错,但是日期是千奇百怪的,部分调用直接报错了

1.3问题排查
代码语言:javascript
复制
    protected Calendar calendar;

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

SimpleDateFormat(下面简称sdf)类内部有一个Calendar对象引用,它用来储存和这个sdf相关的日期信息,例如sdf.parse(dateStr), sdf.format(date) 诸如此类的方法参数传入的日期相关String, Date等等, 都是交友Calendar引用来储存的.这样就会导致一个问题,如果你的sdf是个static的, 那么多个thread 之间就会共享这个sdf, 同时也是共享这个Calendar引用, 并且, 观察 sdf.parse() 方法,你会发现有如下的调用:

代码语言:javascript
复制
Date parse() {
 calendar.clear(); // 清理calendar
 calendar.setTime(); // 设置calendar的时间
 ... // 执行一些操作
 calendar.getTime(); // 获取calendar的时间
}

这里会导致的问题就是, 如果 线程A 调用了 sdf.parse(), 并且进行了 calendar.clear()后还未执行calendar.getTime()的时候,线程B又调用了sdf.parse(), 这时候线程B也执行了sdf.clear()方法, 这样就导致线程A的的calendar数据被清空了(实际上A,B的同时被清空了). 又或者当 A 执行了calendar.clear() 后被挂起, 这时候B 开始调用sdf.parse()并顺利i结束, 这样 A 的 calendar内存储的的date 变成了后来B设置的calendar的date.亦或是并发setTime()等等问题. 这就造成了多线程并发修改的问题

2.问题解决

1.每次方法调用的时候都使用创建一个新的SimpleDateFormat自己用

缺点:如果我们同一线程多次调用格式化方法岂不是创建销毁了很多次SimpleDateFormat?? 并发下一点点资源的损耗都会造成积少成多的情况,所以我们尽量减少重复资源的占用.这种方案可行但是不太好

2.对于单一线程频繁使用SimpleDateFormat的,可以使用ThreadLocal存储用时再取即可
3.使用java8提供的更安全的LocalDateTime (推荐!)

核心思想:基于领域模型驱动设计方法以及不可变类,提供了各种各样的安全类,比如做时间差的Duration,还有LocalDate,LocalTime,LocalDateTime等不可变类,并提供了相互的转换方法

优点:
  • 1.date有的LocalDateTime都有,有非常非常强大的Api,我也基于他的api封装了一些工具类,但是公司代码不好提供,大家可以直接参阅文档
  • 2.安全可靠
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/9/6 上午,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.问题复现
  • 2.问题解决
    • 1.每次方法调用的时候都使用创建一个新的SimpleDateFormat自己用
      • 2.对于单一线程频繁使用SimpleDateFormat的,可以使用ThreadLocal存储用时再取即可
        • 3.使用java8提供的更安全的LocalDateTime (推荐!)
          • 优点:
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档