过去几周我一直在尝试使用guava MapMaker找到理想的缓存实现。请参阅我之前的两个问题here和here,以了解我的思维过程。
根据我所学到的,我的下一次尝试将放弃软值,转而使用maximumSize和expireAfterAccess:
ConcurrentMap<String, MyObject> cache = new MapMaker()
.maximumSize(MAXIMUM_SIZE)
.expireAfterAccess(MINUTES_TO_EXPIRY, TimeUnit.MINUTES)
.makeComputingMap(loadFunction);
,其中
Function<String, MyObject> loadFunction = new Function<String, MyObject>() {
@Override
public MyObject apply(String uidKey) {
return getFromDataBase(uidKey);
}
};
然而,我仍在努力解决的另一个问题是,即使它们的时间很长,这个实现也会驱逐对象,即使它们是强烈可达的。这可能会导致多个对象在环境中浮动相同的UID,这是我不想要的(我相信我想要实现的目标称为规范化)。
据我所知,唯一的答案是有一个额外的地图作为一个内部人员,我可以检查数据对象是否仍然在内存中:
ConcurrentMap<String, MyObject> interner = new MapMaker()
.weakValues()
.makeMap();
并且将修改加载函数:
Function<String, MyObject> loadFunction = new Function<String, MyObject>() {
@Override
public MyObject apply(String uidKey) {
MyObject dataObject = interner.get(uidKey);
if (dataObject == null) {
dataObject = getFromDataBase(uidKey);
interner.put(uidKey, dataObject);
}
return dataObject;
}
};
但是,使用两个映射而不是一个用于缓存似乎效率低下。有没有更复杂的方法来解决这个问题?总的来说,我是以正确的方式解决这个问题,还是应该重新考虑我的缓存策略?
答案 0 :(得分:8)
两个地图是否有效取决于getFromDatabase()的价格以及对象的大小。做这样的事情似乎并不合理。
至于实现,看起来你可能会以稍微不同的方式对你的地图进行分层以获得你想要的行为,并且仍然具有良好的并发属性。
通过第二张地图进行所有访问。
换句话说,到期的映射用于将最近使用的对象子集固定在内存中,而弱引用映射是真正的缓存。
-dg
答案 1 :(得分:0)
我不明白这里的全貌,但有两件事。
鉴于这句话:“这个实现将会驱逐对象,即使它们的时间已经很快就可以到达。这可能导致多个对象在环境中浮动相同的UID,我不这样做想。” - 听起来你只需要使用weakKeys()而不使用定时或基于大小的驱逐。
或者如果您确实希望将“interner”加入其中,我会使用真正的Interners.newWeakInterner
。