正在缓存 JDK 个 TimeZone 实例

Caching JDK TimeZone instances

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

我想到了以下解决方案。它被证明对性能有很大的提升。

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 class 之外缓存 TimeZone 实例是否安全?这意味着 TimeZone/ZoneInfo/ZoneInfoFile 做了一些神奇的事情来内部更新它的缓存,这样我这里的应用程序缓存就与 TimeZone 内部的缓存不一致了。

并且在有人建议之前 - 升级到 JDK 8 Date/time API 和 Joda time 都不是一个选项。

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

这应该有效。不过有一些建议:

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

一旦 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(字符串)
  • 区域信息文件::getZoneInfo(字符串)
  • 区域信息::getTimeZone(字符串)
  • 时区::getTimeZone(字符串)

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