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
How to improve performance of SimpleDateFormat wrapped in ThreadLocal?
提问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 的平均响应时间有所增加。这是预期的吗?或者,我只是在做一些愚蠢的事情?
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
SimpleDateFormat
Not 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
第三方开源库,流行的日期/日历替代品。 - java.time 包
新的,捆绑在Java 8 中,取代旧的 Date/Calendar 类,受 JSR 310 定义的 Joda-Time 的启发。
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 DateTimeFormatter
class 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 ,而不是每次需要响应请求时都这样做。