【问题标题】:SimpleDateFormat - not safe but why exactly? [duplicate]SimpleDateFormat - 不安全,但究竟为什么呢? [复制]
【发布时间】:2016-04-22 13:21:47
【问题描述】:

我最近意识到/了解到SimpleDateFormat 有一些严重的问题,因为 Java 8 不应该再使用了。我的意思是……我有点知道,但从来没有太在意。到目前为止,一切都很好。

但是好吧...我有很多在过去 7 到 8 年编写的遗留代码确实使用 SimpleDateFormat,将许多 SimpleDateFormat 对象存储为静态字段,并使用它们来解析/格式化日期。实际上,这些SimpleDateFormat 实例(无论是否静态)在生产中(这些年来)从来没有遇到过任何问题。

所以...我现在想回顾和分析这段遗留代码,看看其中是否真的存在SimpleDateFormat 的任何危险用途。

所以我的问题是……

SimpleDateFormat到底在什么场景下使用有问题?
我可以获得某种清单,以便我查看我的旧代码并查看我的任何场景是否在那个“尽量避免”列表中?

【问题讨论】:

  • 它不是线程安全的,所以如果两个线程同时使用同一个SimpleDateFormat 对象就会出现问题。
  • 如果代码当前正在运行,则不会突然添加新的缺陷。但是,新的 Date & Time API 更易于正确使用。
  • @jarnbjo 主要是这些描述在这里:stackoverflow.com/questions/6840803/… 嗯......所以......这真的是不必要的麻烦吗?
  • @peter.petrov 您链接的问题讨论了 SimpleDateFormat 类中缺乏线程安全性。这就是你所说的“严重问题”和避免上课的理由吗?如果是这样,那么大部分标准 Java API(集合、磁盘和网络 I/O、AWT/Swing)都存在“严重问题”,必须避免。
  • 在分析遗留代码时,您至少可以在方法主体内整理出所有本地创建的SimpleDateFormat 实例(线程安全)。您只需调查共享实例以评估其使用是否安全(即,在给定上下文中是否有多个线程访问同一实例)。

标签: java multithreading date thread-safety simpledateformat


【解决方案1】:

SimpleDateFormat 不是线程安全的,任何将它放在由多个线程访问的字段的情况都将是一个潜在的问题。它不会对您造成影响,但可能会导致生成错误的结果。

对这种情况进行分类需要检查您将结果用于什么目的以及这些值始终正确的重要性。同样显然,您使用格式化程序的线程越多,出错的可能性就越大,因此多线程但很少有多个用户的应用程序可能会被优先考虑。

【讨论】:

    【解决方案2】:

    例如,一个问题是 SimpleDateFormat 有一个内部字段,该字段设置为您要格式化的日期/日历。

    因此,如果您有两个线程同时使用相同的 SDF 和两个不同的日期,则被格式化的日期可能会在格式化中间发生变化,从而产生一个混合两个日期的字符串。

    这就是下面的例子所模拟的。如果您使用ExecutorService es = Executors.newFixedThreadPool(1);(单线程)运行它,结果集只有两个日期,正如预期的那样。如果您改用ExecutorService es = Executors.newFixedThreadPool(10);(多线程),则结果集可能会有更多日期,这是两个日期的混合。

    例如,在我的机器上,输出是:

    • 单线程(预期结果):

      [1970 年 1 月 2 日 04:46:40,1970 年 1 月 1 日 01:00:00]

    • 多线程:

      [1970 年 1 月 2 日 01:00:00、1970 年 1 月 1 日 04:00:00、1970 年 1 月 2 日 01:00:40、1970 年 1 月 1 日 01:00:40、02- 1970 年 1 月 04:46:00、1970 年 1 月 2 日 01:46:00、1970 年 1 月 1 日 01:00:00、1970 年 1 月 2 日 04:00:00、1970 年 1 月 2 日 01:46 :40, 1970 年 1 月 2 日 04:00:40, 1970 年 1 月 1 日 01:46:00, 1970 年 1 月 1 日 01:46:40, 1970 年 1 月 2 日 04:46:40, 1 月 1 日-1970 04:00:40, 01-Jan-1970 04:46:40, 01-Jan-1970 04:46:00]


    private static final DateFormat FMT = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
    private static final CountDownLatch LATCH = new CountDownLatch(1);
    
    public static void main(String[] args) throws Exception {
      ExecutorService es = Executors.newFixedThreadPool(1);
      Date d1 = new Date(0);
      Date d2 = new Date(100_000_000);
      List<Future<String>> futures = new ArrayList<> ();
      for (int i = 0; i < 10_000; i++) {
        Date d = i % 2 == 0 ? d1 : d2;
        Future<String> f = es.submit(() -> run(d));
        futures.add(f);
      }
      LATCH.countDown();
      es.shutdown();
      es.awaitTermination(5, TimeUnit.SECONDS);
      Set<String> results = new HashSet<> ();
      for (Future<String> f : futures) {
        results.add(f.get());
      }
    
      System.out.println(results);
    }
    
    private static String run(Date d) throws InterruptedException {
      LATCH.await();
      return FMT.format(d);
    }
    

    【讨论】:

      【解决方案3】:

      SimpleDateFormat 不是线程安全的,因此在每个多线程场景(例如 Web 应用程序)中,您不能在常量类中声明一个格式化程序并在您的业务方法中使用它而不会有奇怪错误的风险......特别是当你开始玩时区等。

      【讨论】:

        猜你喜欢
        • 2011-10-14
        • 2019-03-06
        • 2020-02-26
        • 2017-02-11
        • 1970-01-01
        • 1970-01-01
        • 2011-04-29
        • 2012-03-18
        • 2012-04-30
        相关资源
        最近更新 更多