问题描述
假设我们的应用程序中有一个CountryList对象应返回国家/地区列表。加载国家是一项繁重的操作,因此应该缓存该列表。
let's say we have a CountryList object in our application that should return the list of countries. The loading of countries is a heavy operation, so the list should be cached.
附加要求:
- CountryList应该是线程安全的
- CountryList应加载延迟(仅按需)
- CountryList应支持缓存失效
- 应优化CountryList考虑到缓存很少会失效
- CountryList should be thread-safe
- CountryList should load lazy (only on demand)
- CountryList should support the invalidation of the cache
- CountryList should be optimized considering that the cache will be invalidated very rarely
我想出了以下解决方案:
I came up with the following solution:
public class CountryList {
private static final Object ONE = new Integer(1);
// MapMaker is from Google Collections Library
private Map<Object, List<String>> cache = new MapMaker()
.initialCapacity(1)
.makeComputingMap(
new Function<Object, List<String>>() {
@Override
public List<String> apply(Object from) {
return loadCountryList();
}
});
private List<String> loadCountryList() {
// HEAVY OPERATION TO LOAD DATA
}
public List<String> list() {
return cache.get(ONE);
}
public void invalidateCache() {
cache.remove(ONE);
}
}
您如何看待它?你觉得它有什么坏处吗?还有其他办法吗?我怎样才能让它变得更好?在这种情况下我应该寻找另一种解决方案吗?
What do you think about it? Do you see something bad about it? Is there other way to do it? How can i make it better? Should i look for totally another solution in this cases?
谢谢。
推荐答案
谢谢大家,特别是对于提出这个想法的用户 gid 。
Thanks you all guys, especially to user "gid" who gave the idea.
我的目标考虑到invalidate()操作将被称为非常罕见,是为了优化get()操作的性能。
My target was to optimize the performance for the get() operation considering the invalidate() operation will be called very rare.
我写了一个测试类,它启动了16个线程,每个调用get() - 操作一百万次。在这个课程中,我在我的2核机器上描述了一些实现。
I wrote a testing class that starts 16 threads, each calling get()-Operation one million times. With this class I profiled some implementation on my 2-core maschine.
测试结果
Implementation Time
no synchronisation 0,6 sec
normal synchronisation 7,5 sec
with MapMaker 26,3 sec
with Suppliers.memoize 8,2 sec
with optimized memoize 1,5 sec
1)无同步不是线程安全的,但为我们提供了我们可以比较的最佳表现。
1) "No synchronisation" is not thread-safe, but gives us the best performance that we can compare to.
@Override
public List<String> list() {
if (cache == null) {
cache = loadCountryList();
}
return cache;
}
@Override
public void invalidateCache() {
cache = null;
}
2)正常同步 - 相当不错的性能,标准的简单实施
2) "Normal synchronisation" - pretty good performace, standard no-brainer implementation
@Override
public synchronized List<String> list() {
if (cache == null) {
cache = loadCountryList();
}
return cache;
}
@Override
public synchronized void invalidateCache() {
cache = null;
}
3)使用MapMaker - 性能非常差。
3) "with MapMaker" - very poor performance.
请在顶部查看我的问题代码。
See my question at the top for the code.
4)with Suppliers.memoize - 良好的表现。但由于性能相同正常同步,我们需要优化它或只使用正常同步。
4) "with Suppliers.memoize" - good performance. But as the performance the same "Normal synchronisation" we need to optimize it or just use the "Normal synchronisation".
请参阅用户gid代码的答案。
See the answer of the user "gid" for code.
5)使用优化的memoize - 与无同步实现相当的执行,但是线程安全的执行。这是我们需要的。
5) "with optimized memoize" - the performnce comparable to "no sync"-implementation, but thread-safe one. This is the one we need.
缓存类本身:
(此处使用的供应商界面来自Google Collections Library,它只有一个方法获取()。参见)
public class LazyCache<T> implements Supplier<T> {
private final Supplier<T> supplier;
private volatile Supplier<T> cache;
public LazyCache(Supplier<T> supplier) {
this.supplier = supplier;
reset();
}
private void reset() {
cache = new MemoizingSupplier<T>(supplier);
}
@Override
public T get() {
return cache.get();
}
public void invalidate() {
reset();
}
private static class MemoizingSupplier<T> implements Supplier<T> {
final Supplier<T> delegate;
volatile T value;
MemoizingSupplier(Supplier<T> delegate) {
this.delegate = delegate;
}
@Override
public T get() {
if (value == null) {
synchronized (this) {
if (value == null) {
value = delegate.get();
}
}
}
return value;
}
}
}
使用示例:
public class BetterMemoizeCountryList implements ICountryList {
LazyCache<List<String>> cache = new LazyCache<List<String>>(new Supplier<List<String>>(){
@Override
public List<String> get() {
return loadCountryList();
}
});
@Override
public List<String> list(){
return cache.get();
}
@Override
public void invalidateCache(){
cache.invalidate();
}
private List<String> loadCountryList() {
// this should normally load a full list from the database,
// but just for this instance we mock it with:
return Arrays.asList("Germany", "Russia", "China");
}
}
这篇关于java中一个对象的线程安全缓存的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!