Java 如何提高包装在 ThreadLocal 中的 SimpleDateFormat 的性能?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22098904/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 13:35:58  来源:igfitidea点击:

How to improve performance of SimpleDateFormat wrapped in ThreadLocal?

javaperformanceconcurrencysimpledateformatthread-local

提问by Debajyoti Roy

This is on Java 7 (51) on RHEL with 24 cores We are noticing a rise in average response times of a java SimpleDateFormat wrapped in thread local as we increase the thread pool size. Is this expected? or, I am just doing something stupid ?

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

enter image description here

在此处输入图片说明

Test program

测试程序

    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 class:

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

Someone suggested to take out the AtomicLong so just wanted to share that it is not playing any role in increasing the average time:

有人建议去掉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

回答by Martin Wilson

Creating an instance of SimpleDateFormat is very expensive (this articleshows some profiling/benchmarking). If this is true, compared with the parsing of the dates into strings, then it follows that as you increase the number of threads (and therefore the number of SimpleDateFormat instances as they are threadlocals) your average time is going to increase.

创建 SimpleDateFormat 的实例非常昂贵(本文展示了一些分析/基准测试)。如果这是真的,与将日期解析为字符串相比,那么随着您增加线程数量(因此,SimpleDateFormat 实例的数量,因为它们是线程局部变量),您的平均时间将会增加。

回答by Basil Bourque

SimpleDateFormatNot Thread-Safe

SimpleDateFormat不是线程安全的

As the correct answer by Martin Wilsonstates, instantiating a SimpleDateFormat is relatively expensive.

正如Martin Wilson所说的正确答案,实例化 SimpleDateFormat 的成本相对较高。

Knowing that your first thought might be, "Well, let's cache an instance for re-use.". Nice thought, but beware: The SimpleDateFormat class in not thread-safe. So says the class documentationunder its Synchronizationheading.

知道您的第一个想法可能是,“好吧,让我们缓存一个实例以供重用。”。不错的想法,但要注意: SimpleDateFormat 类不是线程安全的同步标题下的类文档如是说。

Joda-Time

乔达时间

A better solution is to avoid the notoriously troublesome (and now outmoded) java.util.Date, .Calendar, and SimpleDateFormat classes. Instead use either:

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

  • Joda-Time
    third-party open-source library, popular replacement for Date/Calendar.
  • java.time package
    New, bundled in Java 8, supplanting the old Date/Calendar classes, inspired by Joda-Time, defined by JSR 310.

Joda-Time is intentionally built to be thread-safe, largely through use of immutable objects. There are some mutable classes, but those are not usually used.

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

This other question on StackOverflowexplains that the DateTimeFormatterclass is indeed thread-safe. So you can create one instance, cache it, and let all your threads use that formatter without adding any extra synchronization or other concurrency controls.

在计算器上的其他问题解释说,这个DateTimeFormatter班确实是线程安全的。因此,您可以创建一个实例,缓存它,并让您的所有线程使用该格式化程序,而无需添加任何额外的同步或其他并发控制。

回答by cruftex

Another approach to speed up your formatting is to cache the formatted result. This considers the fact, that there are usually not so many different dates to format. If you split the formatting of date and time, it is even a better candidate for caching.

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

The downside of this is, that normal Java cache implementations, like EHCache, are to slow, the cache access just takes longer then the formatting.

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

There is another cache implementation around that has access times on par with a HashMap. In this case you get a nice speed up. Here you find my proof of concept tests: https://github.com/headissue/cache2k-benchmark/blob/master/zoo/src/test/java/org/cache2k/benchmark/DateFormattingBenchmark.java

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

Maybe this can be a solution within your scenario.

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

Disclaimer: I am working on cache2k....

免责声明:我正在研究cache2k....

回答by Debajyoti Roy

Our usecase was write once(single thread) and read many times(concurrently). So i converted Date to String at the time of storing the data, instead of doing this each time a request needs to be responded.

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