在长期服务器应用程序中有效地缓存和更新当前日期

时间:2012-02-16 16:14:56

标签: java performance

我有一个长期存在的服务器应用程序,旨在以最短的停机时间运行(例如,仅停止维护的24/7操作停止)。该应用程序必须能够每秒处理数千个请求,因此性能是一个问题。

为每个请求提供服务。应用程序的一部分需要知道当前日期是什么(虽然不是时间),并且由于第三方API,它必须存储在java.util.Date对象中。

但是,Date对象的构造成本很高,因此为每个请求创建一个新对象听起来不合理。

在请求之间共享Date对象并每天更新一次意味着在启动时只需要创建一个对象(每个服务器工作线程),但是如何以安全的方式更新它?

例如,使用在午夜之后运行的ScheduledExecutorService可以增加Date,但是将线程同步引入混合中:Date对象现在在主线程和ScheduledExecutorService生成运行更新任务的线程。

同步的2个线程引入了另一个性能头痛,由于在成千上万的请求之间的共享资源的争用的被服务的可能性(每天更新线程的单个执行是不太关心的,因为它仅发生一次每一天,不像我们每天服务的数百万个请求。

所以,我的问题是确保应用程序始终知道当前日期是什么的最有效方法是什么,即使连续几周连续运行也是如此?

5 个答案:

答案 0 :(得分:2)

我认为你所讨论的昂贵的构造函数是new Date(),它调用System.currentTimeMillis()。最简单的方法是使用new Date(long)字段中存储的值volatile。然后,外部线程可以在适当的时间更新此字段,其他线程将根据此更新的值创建其Date个对象。

编辑:虽然当前的问题似乎是过早优化,但System.currentTimeMillis()有时可能成为瓶颈。如果您遇到这种情况,请检查此链接:http://dow.ngra.de/2008/10/27/when-systemcurrenttimemillis-is-too-slow/

答案 1 :(得分:2)

不要为此优化而烦恼。它没有可衡量的影响。浪费时间。

答案 2 :(得分:1)

http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicReference.html可以避免同步开销。如果有单个线程执行请求,那么您可以在同一个线程上执行任务来更新日期而不会产生任何同步开销。如果有多个线程,那么也可以通过使日期字段线程为本地并在每个线程中单独更新它来应用相同的想法。

答案 3 :(得分:1)

我强烈建议您使用caching feature from Google's Guava library。缓存库具有良好的并发支持,它为您的问题提供了几种替代方法。

一种方法,如果您的用例可以接受陈旧日期(例如,对于新一天的前100秒,如果您的应用程序仍然认为它是前一天就可以了),您可以使用

LoadingCache<Object, Date> tDateCache 
    = CacheBuilder.newBuilder()
                  .maximumSize(1)
                  .expireAfterWrite(100, TimeUnit.SECONDS)
                  .build( new CacheLoader<Object, Date>() {
                              public Date load(Object object) {
                                     return new Date();
                                     }
                              });

使用tDateCache.get(tDateCache)访问缓存日期。 expireAfterWrite(100, TimeUnit.SECONDS)参数将导致您的缓存每100秒自动刷新一次。

每次检测到滚动日时,您还可以有一个监视器线程自动调用Cache.invalidateAll(),这将导致重新调用缓存加载器的加载方法并创建新日期。无需担心并发性,库会为您处理。

答案 4 :(得分:1)

@chrisbunney,这个帖子的一些答案建议使用专用并发类,但如果你真的需要按照你最初的要求缓存一个日期,那么你只需要一件事:{{1} } keyword。

如果你需要一个原子检查和交换操作,

volatile是好的,但在这种情况下,你没有检查任何东西。您只是设置对新值的引用,并且您希望该值对所有线程可见。这就是AtomicReference所做的。

如果要修改现有对象的内部状态,则可能需要锁定。或者,如果您要读取现有volatile的值,请根据该值进行一些计算,并生成新的Date对象,再次进行某种类型的锁定(或类似{{ 1}})是必要的。但你不是那样做的;再次,你只是替换了一个引用,而不考虑它以前的值。

通常,如果您只有一个线程替换了一个值,而其他线程只读取该值,Date就足够了。不需要其他并发控制。或者,如果您有多个可以替换值的线程,但它们只替换,而不是就地修改,并且它们替换它而不考虑其先前的值,那么AtomicReference就足够了。不需要任何其他东西。