【问题标题】:How to improve performance of SimpleDateFormat wrapped in ThreadLocal?如何提高包裹在 ThreadLocal 中的 SimpleDateFormat 的性能?
【发布时间】:2014-04-01 16:13:38
【问题描述】:

这是在 RHEL 上的 Java 7 (51) 上,具有 24 个内核 随着线程池大小的增加,我们注意到包裹在线程本地的 java SimpleDateFormat 的平均响应时间有所增加。这是预期的吗?或者,我只是在做一些愚蠢的事情?

测试程序

    public class DateFormatterLoadTest {
        private static final Logger LOG = Logger.getLogger(DateFormatterLoadTest .class);
        private final static int CONCURRENCY = 10;

        public static void main(String[] args) throws Exception {
            final AtomicLong total = new AtomicLong(0);
            ExecutorService es = Executors.newFixedThreadPool(CONCURRENCY);
            final CountDownLatch cdl = new CountDownLatch(CONCURRENCY);
            for (int i = 0; i < CONCURRENCY; i++) {
                es.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            int size = 65000;
                            Date d = new Date();

                            long time = System.currentTimeMillis();
                            for (int i = 0; i < size; i++) {
                                String sd = ISODateFormatter.convertDateToString(d);
                                assert (sd != null);
                            }
                            total.addAndGet((System.currentTimeMillis() - time));

                        } catch (Throwable t) {
                            t.printStackTrace();
                        } finally {
                            cdl.countDown();
                        }
                    }
                });
            }
            cdl.await();
            es.shutdown();
            LOG.info("TOTAL TIME:" + total.get());
            LOG.info("AVERAGE TIME:" + (total.get() / CONCURRENCY));
        }
    }

DateFormatter 类:

public class ISODateFormatter {
    private static final Logger LOG = Logger.getLogger(ISODateFormatter.class);

    private static ThreadLocal<DateFormat> dfWithTZ = new ThreadLocal<DateFormat>() {
        @Override
        public DateFormat get() {
            return super.get();
        }

        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",
                    Locale.ENGLISH);
        }

        @Override
        public void remove() {
            super.remove();
        }

        @Override
        public void set(DateFormat value) {
            super.set(value);
        }

    };

    public static String convertDateToString(Date date) {
        if (date == null) {
            return null;
        }
        try {
            return dfWithTZ.get().format(date);
        } catch (Exception e) {
            LOG.error("!!! Error parsing dateString: " + date, e);
            return null;
        }
    }
}

有人建议取出 AtomicLong 所以只是想分享它对增加平均时间没有任何作用:

##NOT USING ATOMIC LONG##
2014-02-28 11:03:52,790 [pool-1-thread-1] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:328
2014-02-28 11:03:52,868 [pool-1-thread-6] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:406
2014-02-28 11:03:52,821 [pool-1-thread-2] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:359
2014-02-28 11:03:52,821 [pool-1-thread-8] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:359
2014-02-28 11:03:52,868 [pool-1-thread-4] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:406
2014-02-28 11:03:52,915 [pool-1-thread-5] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:453
2014-02-28 11:03:52,930 [pool-1-thread-7] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:468
2014-02-28 11:03:52,930 [pool-1-thread-3] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:468
2014-02-28 11:03:52,930 [main] INFO  net.ahm.graph.DateFormatterLoadTest  - CONCURRENCY:8

##USING ATOMIC LONG##
2014-02-28 11:02:53,852 [main] INFO  net.ahm.graph.DateFormatterLoadTest  - TOTAL TIME:2726
2014-02-28 11:02:53,852 [main] INFO  net.ahm.graph.DateFormatterLoadTest  - CONCURRENCY:8
2014-02-28 11:02:53,852 [main] INFO  net.ahm.graph.DateFormatterLoadTest  - AVERAGE TIME:340

##NOT USING ATOMIC LONG##
2014-02-28 11:06:57,980 [pool-1-thread-3] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:312
2014-02-28 11:06:58,339 [pool-1-thread-8] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:671
2014-02-28 11:06:58,339 [pool-1-thread-4] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:671
2014-02-28 11:06:58,307 [pool-1-thread-7] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:639
2014-02-28 11:06:58,261 [pool-1-thread-6] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:593
2014-02-28 11:06:58,105 [pool-1-thread-15] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:437
2014-02-28 11:06:58,089 [pool-1-thread-13] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:421
2014-02-28 11:06:58,073 [pool-1-thread-1] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:405
2014-02-28 11:06:58,073 [pool-1-thread-12] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:405
2014-02-28 11:06:58,042 [pool-1-thread-14] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:374
2014-02-28 11:06:57,995 [pool-1-thread-2] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:327
2014-02-28 11:06:57,995 [pool-1-thread-16] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:327
2014-02-28 11:06:58,385 [pool-1-thread-10] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:717
2014-02-28 11:06:58,385 [pool-1-thread-11] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:717
2014-02-28 11:06:58,417 [pool-1-thread-9] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:749
2014-02-28 11:06:58,418 [pool-1-thread-5] INFO  net.ahm.graph.DateFormatterLoadTest  - THREAD TIME:750
2014-02-28 11:06:58,418 [main] INFO  net.ahm.graph.DateFormatterLoadTest  - CONCURRENCY:16

##USING ATOMIC LONG##
2014-02-28 11:07:57,510 [main] INFO  net.ahm.graph.DateFormatterLoadTest  - TOTAL TIME:9365
2014-02-28 11:07:57,510 [main] INFO  net.ahm.graph.DateFormatterLoadTest  - CONCURRENCY:16
2014-02-28 11:07:57,510 [main] INFO  net.ahm.graph.DateFormatterLoadTest  - AVERAGE TIME:585

【问题讨论】:

  • 本地线程的实例如果不是空实例则返回该实例对数据进行操作或按需初始化一个新实例。
  • 能否将 SimpleDateFormat sdf 作为私有静态变量添加到 ISODateFormatter 并在静态块中对其进行初始化?然后每次调用 initialValue() 时都会返回相同的实例。
  • 既然您已经费尽心思构建了一个线束,您不妨对FastDateFormatDateTimeFormatter 进行基准测试
  • @LanceJava: user3301492 建议拥有一个私有静态 SimpleDateFormatter 并在 initialValue 中返回该 same 实例。这意味着 same 实例将在每个线程中使用,这意味着 SimpleDateFormatter 将不再以线程安全的方式使用。
  • 您有 24 个内核,池中最多有 64 个线程。由于您拥有的线程多于可用内核,线程开始相互竞争,并且每个内核上的上下文切换开始出现。在我看来,这种上下文切换会增加您的响应时间,这很正常。

标签: java performance concurrency simpledateformat thread-local


【解决方案1】:

创建 SimpleDateFormat 的实例非常昂贵(this article 显示了一些分析/基准测试)。如果这是真的,那么与将日期解析为字符串相比,随着线程数量的增加(以及作为线程本地对象的 SimpleDateFormat 实例的数量),您的平均时间将会增加。

【讨论】:

    【解决方案2】:

    SimpleDateFormat 不是线程安全的

    正如correct answer by Martin Wilson 所述,实例化 SimpleDateFormat 相对昂贵。

    知道您的第一个想法可能是“好吧,让我们缓存一个实例以供重复使用。”。不错的想法,但要注意:不是线程安全的 中的 SimpleDateFormat 类。 the class documentation 在其同步标题下也是如此。

    乔达时间

    更好的解决方案是避免使用众所周知的麻烦(现在已经过时)java.util.Date、.Calendar 和 SimpleDateFormat 类。而是使用:

    • Joda-Time
      第三方开源库,日期/日历的流行替代品。
    • java.time package
      新的,捆绑在 Java 8 中,取代了旧的 Date/Calendar 类,灵感来自 JSR 310 定义的 Joda-Time。

    Joda-Time 被有意构建为线程安全的,主要是通过使用immutable objects。有一些可变类,但通常不使用。

    这个other question on StackOverflow 解释了DateTimeFormatter 类确实是线程安全的。因此,您可以创建一个实例,将其缓存,并让所有线程使用该格式化程序,而无需添加任何额外的同步或其他并发控制。

    【讨论】:

    • OP 在 ThreadLocal 中缓存 SimpleDateFormat。因此,每个线程都有自己的 SimpleDateFormat 实例,不与其他线程共享。因此,这是重用 SimpleDateFormat 的好方法,而不会冒线程安全问题的风险。
    【解决方案3】:

    另一种加快格式化速度的方法是缓存格式化的结果。这考虑了一个事实,即通常没有那么多不同的日期要格式化。如果拆分日期和时间的格式,它甚至是更好的缓存候选。

    这样做的缺点是,普通的 Java 缓存实现,如 EHCache,速度很慢,缓存访问比格式化需要更长的时间。

    还有另一个缓存实现,它的访问时间与 HashMap 相当。在这种情况下,你会得到很好的加速。在这里你可以找到我的概念验证测试:https://github.com/headissue/cache2k-benchmark/blob/master/zoo/src/test/java/org/cache2k/benchmark/DateFormattingBenchmark.java

    也许这可以成为您方案中的解决方案。

    免责声明:我正在处理cache2k....

    【讨论】:

    【解决方案4】:

    我们的用例是一次写入(单线程)和多次读取(并发)。所以我在存储数据时将日期转换为字符串,而不是每次需要响应请求时都这样做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-14
      • 2018-01-21
      • 2014-06-05
      • 1970-01-01
      • 1970-01-01
      • 2013-09-13
      相关资源
      最近更新 更多