我有一个拥有Guava LoadingCache<String, Integer>
的Java类,在那个缓存中,我打算存储两件事:活跃员工当天工作的平均时间和效率。我正在缓存这些值,因为每次请求进入时计算都会很昂贵。而且,每分钟都会刷新缓存的内容(refreshAfterWrite
)。
我想在这种情况下使用CacheLoader
,但是,其加载方法只为每个键加载一个值。在我的CacheLoader
中,我打算做以下事情:
private Service service = new Service();
public Integer load(String key) throws Exception {
if (key.equals("employeeAvg"))
return calculateEmployeeAvg(service.getAllEmployees());
if (key.equals("employeeEff"))
return calculateEmployeeEff(service.getAllEmployees());
return -1;
}
对我来说,我发现效率非常低,因为为了加载这两个值,我必须调用service.getAllEmployees()
两次,因为如果我错了,请纠正我,CacheLoader
应该是无状态的。
这使我想到使用LoadingCache.put(key, value)
方法,所以我可以创建一个实用程序方法,调用service.getAllEmployees()
一次并动态计算值。但是,如果我使用LoadingCache.put()
,我将不会拥有refreshAfterWrite
功能,因为它依赖于缓存加载器。
如何提高效率?
答案 0 :(得分:3)
似乎您的问题源于使用字符串来表示值类型(Effective Java Item 50)。相反,请考虑定义存储此数据的正确值类型,并使用memoizing Supplier
来避免重新计算它们。
public static class EmployeeStatistics {
private final int average;
private final int efficiency;
// constructor, getters and setters
}
Supplier<EmployeeStatistics> statistics = Suppliers.memoize(
new Supplier<EmployeeStatistics>() {
@Override
public EmployeeStatistics get() {
List<Employee> employees = new Service().getAllEmployees();
return new EmployeeStatistics(
calculateEmployeeAvg(employees),
calculateEmployeeEff(employees));
}});
您甚至可以在EmployeeStatistics
中移动这些计算方法,只需将所有员工传递给构造函数,然后让它计算相应的数据。
如果您需要配置超过Suppliers.memoize()
或Suppliers.memoizeWithExpiration()
的缓存行为,请考虑这种类似的模式,这会隐藏您在内部使用Cache
这一事实Supplier
:
Supplier<EmployeeStatistics> statistics = new Supplier<EmployeeStatistics>() {
private final Object key = new Object();
private final LoadingCache<Object, EmployeeStatistics> cache =
CacheBuilder.newBuilder()
// configure your builder
.build(
new CacheLoader<Object, EmployeeStatistics>() {
public EmployeeStatistics load(Object key) {
// same behavior as the Supplier above
}});
@Override
public EmployeeStatistics get() {
return cache.get(key);
}};
答案 1 :(得分:2)
但是,如果我使用
LoadingCache.put()
,我就不会拥有refreshAfterWrite
功能,因为它依赖于缓存加载器。
我不确定,但您可以从load
方法中调用它。我的意思是,像你一样计算请求的值,在另一个计算put
。然而,这感觉很糟糕。
如果service.getAllEmployees
很贵,那么你可以缓存它。如果calculateEmployeeAvg
和calculateEmployeeEff
都很便宜,那么在需要时重新计算它们。否则,看起来你可以使用两个缓存。
我想,一次计算两个值的方法可能是一个合理的解决方案。创建一个类似于Pair的类,聚合它们并将其用作缓存值。只有一把钥匙。
关于你自己的解决方案,它可能与
一样微不足道class EmployeeStatsCache {
private long validUntil;
private List<Employee> employeeList;
private Integer employeeAvg;
private Integer employeeEff;
private boolean isValid() {
return System.currentTimeMillis() <= validUntil;
}
private synchronized List<Employee> getEmployeeList() {
if (!isValid || employeeList==null) {
employeeList = service.getAllEmployees();
validUntil = System.currentTimeMillis() + VALIDITY_MILLIS;
}
return employeeList;
}
public synchronized int getEmployeeAvg() {
if (!isValid || employeeAvg==null) {
employeeAvg = calculateEmployeeAvg(getEmployeeList());
}
return employeeAvg;
}
public synchronized int getEmployeeEff() {
if (!isValid || employeeAvg==null) {
employeeAvg = calculateEmployeeEff(getEmployeeList());
}
return employeeAvg;
}
}
您可能希望在私有最终字段上进行同步,而不是synchronized
种方法。还有其他可能性(例如Atomic*
),但基本设计可能比调整番石榴Cache
更简单。
现在,我在Guava看到了Suppliers#memoizeWithExpiration
。这可能更简单。