【问题标题】:Caching JDK TimeZone instances缓存 JDK TimeZone 实例
【发布时间】:2015-06-02 12:25:53
【问题描述】:

由于 TimeZone::getTimeZone(String) 完全成为瓶颈,我们面临着严重的性能问题。它正在对类本身加锁(因为方法是静态的),目前几乎所有的执行线程都在等待获取这个锁。

我想出了以下解决方案。事实证明,它可以极大地提高性能。

private static final Object TIME_ZONE_CACHE_LOCK = new Object();
private static volatile Map<String, TimeZone> ourCachedTimeZones = new HashMap<>();

public static TimeZone getTimeZoneFor(String timeZoneId)
{
    TimeZone timeZone = ourCachedTimeZones.get(timeZoneId);

    if (timeZone == null)
    {
        TimeZone newTimeZone = TimeZone.getTimeZone(timeZoneId);

        synchronized (TIME_ZONE_CACHE_LOCK)
        {
            timeZone = ourCachedTimeZones.get(timeZoneId);

            if (timeZone == null)
            {
                timeZone = newTimeZone;
                Map<String, TimeZone> cachedTimeZones = new HashMap<>(ourCachedTimeZones);
                cachedTimeZones.put(timeZoneId, timeZone);
                ourCachedTimeZones = cachedTimeZones;
            }
        }
    }

    // Clone is needed since TimeZone is not thread-safe
    return (TimeZone) timeZone.clone();
}

我的问题:有谁知道在 TimeZone 类之外缓存 TimeZone 实例是否安全?这意味着 TimeZone/ZoneInfo/ZoneInfoFile 会在内部更新其缓存,因此我在此处拥有的应用程序缓存与 TimeZone 内部的缓存不一致。

在有人建议之前 - 升级到 JDK 8 日期/时间 API 和 Joda 时间都不是一个选项。

在有人抱怨之前 :-) - 我知道通常不建议使用双重检查习语。

【问题讨论】:

  • 除此之外——你在锁外使用HashMap(对多线程不安全)。不要那样做。当我最初写评论时,我没有注意到你每次都创建一个新地图,但即便如此这仍然是“聪明”的代码,很可能是非常微妙的错误。为什么不使用为并发访问设计的地图?即使它偶尔意味着重复工作,它也会使代码更简单并且更安全。
  • 它只是读取的并发访问 - 任何更改都是由 volatile 字段的原子集操作进行的。
  • @Cowboy volatile 是对地图的引用,而不是地图的内部变量。
  • (顺便说一句,Guava 对缓存有很多支持。听起来你应该看看。)
  • 我知道 volatile 的作用,但是永远不会在 volatile 引用的地图上进行更改。使用旧值创建一个新映射,然后自动分配给 volatile 字段。

标签: java timezone


【解决方案1】:

一旦 JVM 从文件加载时区,它们就会被固定。 检查 Oracle 的时区更新实用程序: http://www.oracle.com/technetwork/java/javase/tzupdater-readme-136440.html

您需要退回 JVM 以应用更新。

请注意,缓存将是您的目的的缓存。其他一些库仍然可能(重新)使用慢速路径加载 TimeZone。

但这里有一个重要的警告: 你不会有太多收获。 HotSpot 实现已经缓存:

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/sun/util/calendar/ZoneInfoFile.java/#125

调用栈是:

  • ZoneInfoFile::getZoneInfo0(String)
  • ZoneInfoFile::getZoneInfo(String)
  • ZoneInfo::getTimeZone(String)
  • TimeZone::getTimeZone(String)

因为返回的 Zone 是可变的,所以是防御性副本。

【讨论】:

  • 谢谢,我解释说像我一样缓存实例是完全可以的,即在 VM 加载 TimeZone 之后,TimeZone 缓存之间不会出现不匹配(正如您正确指出的那样) .关于大警告 - 我会收获很多。我已经验证了性能提升。
  • @Cowboy 我会对配置文件感兴趣,我真的想知道您现在跳过的代码的哪一部分是导致性能下降的原因。你到底使用哪个 JDK?
  • 我正在使用 JDK7。问题是获取时区时的静态同步(实际上,在这种情况下是 GMT+/-XXXX 时区偏移)。测试是在吞吐量从 4k/s 到 18k/s 的真实场景中完成的。是的,这听起来不可思议,但这就是结果。
【解决方案2】:

这应该可行。不过建议很少:

  1. 您可以使用并发哈希映射
  2. 或者你可以使用 Guava (https://code.google.com/p/guava-libraries/wiki/CachesExplained)
  3. 我之前已经尝试过,并且我为缓存条目使用了 1 天的 TTL。每天都会刷新缓存(延迟加载)。

【讨论】:

  • 您拥有 TTL 是出于某种原因还是仅仅是预防措施?
  • 只是作为预防措施。我还没有读到任何关于这些时区实例变得陈旧的消息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-02
  • 2017-08-04
  • 1970-01-01
  • 1970-01-01
  • 2011-04-08
  • 1970-01-01
相关资源
最近更新 更多