【发布时间】: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 字段。