java中一个对象的线程安全缓存
假设我们的应用程序中有一个CountryList对象,它应该返回国家列表。加载国家是一项繁重的操作,因此应该缓存列表 其他要求:java中一个对象的线程安全缓存,java,caching,lazy-loading,Java,Caching,Lazy Loading,假设我们的应用程序中有一个CountryList对象,它应该返回国家列表。加载国家是一项繁重的操作,因此应该缓存列表 其他要求: CountryList应该是线程安全的 CountryList应加载lazy(仅在需要时) CountryList应支持缓存失效 考虑到缓存很少会失效,应该优化CountryList 我提出了以下解决方案: public class CountryList { private static final Object ONE = new Integer(1)
- CountryList应该是线程安全的
- CountryList应加载lazy(仅在需要时)
- CountryList应支持缓存失效
- 考虑到缓存很少会失效,应该优化CountryList
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);
}
}
公共类国家列表{
私有静态最终对象1=新整数(1);
//MapMaker来自谷歌收藏库
私有地图缓存=新地图生成器()
.初始容量(1)
.makeComputingMap(
新函数(){
@凌驾
公共列表应用(对象来自){
返回loadCountryList();
}
});
私有列表loadCountryList(){
//加载数据的繁重操作
}
公开名单(){
返回cache.get(一个);
}
公共无效{
缓存。删除(一个);
}
}
你觉得怎么样?你看到它有什么不好的地方吗?还有别的办法吗?我怎样才能做得更好?在这种情况下,我是否应该寻找完全不同的解决方案
谢谢。这在我看来没问题(我假设MapMaker来自google collections?)理想情况下,您不需要使用地图,因为您没有真正的密钥,但由于实现对任何调用方都是隐藏的,所以我不认为这有什么大不了的。每当我需要缓存某些内容时,我都喜欢使用。 使用这种模式可以分离关注点。你的原创 对象可能与延迟加载有关。您的代理(或监护人)对象 可以负责缓存的验证 详细内容:
- 定义线程安全的对象CountryList类,最好使用同步块或其他锁
- 将此类的接口提取到CountryQueryable接口中
- 定义实现CountryQueryable的另一个对象CountryListProxy
- 只允许实例化CountryListProxy,并且只允许引用它 通过它的接口
public interface CountryQueryable {
public void operationA();
public String operationB();
}
public class CountryList implements CountryQueryable {
private boolean loaded;
public CountryList() {
loaded = false;
}
//This particular operation might be able to function without
//the extra loading.
@Override
public void operationA() {
//Do whatever.
}
//This operation may need to load the extra stuff.
@Override
public String operationB() {
if (!loaded) {
load();
loaded = true;
}
//Do whatever.
return whatever;
}
private void load() {
//Do the loading of the Lazy load here.
}
}
public class CountryListProxy implements CountryQueryable {
//In accordance with the Proxy pattern, we hide the target
//instance inside of our Proxy instance.
private CountryQueryable actualList;
//Keep track of the lazy time we cached.
private long lastCached;
//Define a tolerance time, 2000 milliseconds, before refreshing
//the cache.
private static final long TOLERANCE = 2000L;
public CountryListProxy() {
//You might even retrieve this object from a Registry.
actualList = new CountryList();
//Initialize it to something stupid.
lastCached = Long.MIN_VALUE;
}
@Override
public synchronized void operationA() {
if ((System.getCurrentTimeMillis() - lastCached) > TOLERANCE) {
//Refresh the cache.
lastCached = System.getCurrentTimeMillis();
} else {
//Cache is okay.
}
}
@Override
public synchronized String operationB() {
if ((System.getCurrentTimeMillis() - lastCached) > TOLERANCE) {
//Refresh the cache.
lastCached = System.getCurrentTimeMillis();
} else {
//Cache is okay.
}
return whatever;
}
}
public class Client {
public static void main(String[] args) {
CountryQueryable queryable = new CountryListProxy();
//Do your thing.
}
}
这是使用ComputingMap的简单方法。您只需要一个非常简单的实现,其中所有方法都是同步的,您应该很好。这显然会阻止第一个线程命中它(获取它),以及在第一个线程加载缓存时命中它的任何其他线程(如果有人调用invalidateCache,情况也是如此——您还应该决定invalidateCache是应该重新加载缓存,还是将其清空,让再次获取缓存的第一次尝试阻塞),但所有线程都应该顺利通过。我不确定映射的用途。当我需要一个懒惰的缓存对象时,我通常会这样做:
public class CountryList
{
private static List<Country> countryList;
public static synchronized List<Country> get()
{
if (countryList==null)
countryList=load();
return countryList;
}
private static List<Country> load()
{
... whatever ...
}
public static synchronized void forget()
{
countryList=null;
}
}
公共类国家列表
{
私有静态列表countryList;
公共静态同步列表get()
{
如果(countryList==null)
countryList=load();
返回国家列表;
}
私有静态列表加载()
{
无论什么
}
公共静态同步的void忘记()
{
countryList=null;
}
}
我想这和你正在做的事情很相似,只是简单一点。如果你需要地图和你简化的地图,好的
如果希望它是线程安全的,那么应该同步get和forget
你觉得它怎么样?你觉得它有什么不好的地方吗
Bleah-您使用的是一个复杂的数据结构MapMaker,它具有多个功能(映射访问、并发友好访问、延迟构建值等),因为您所追求的是一个功能(延迟创建单个构建对象)
虽然重用代码是一个很好的目标,但这种方法增加了额外的开销和复杂性。此外,当未来的维护人员看到地图数据结构时,会误导他们认为其中有一个键/值地图,而实际上只有一件事(国家列表).简单性、可读性和清晰性是未来可维护性的关键
还有其他方法吗?我怎样才能做得更好?在这种情况下,我是否应该寻找完全不同的解决方案
看起来您在关注延迟加载。请查看其他延迟加载问题的解决方案。例如,此解决方案涵盖了经典的双重检查方法(请确保您使用的是Java 1.5或更高版本):
与其简单地在这里重复解决方案代码,我认为阅读关于延迟加载的讨论非常有用,可以通过反复检查来扩展您的知识库。(很抱歉,如果这是夸大其词-只是尝试教鱼而不是喂东西等等…有一个库(来自)-其中一个名为.LazyReference的util类是对一个可以延迟创建的对象的引用(在第一次获取时)。它保证线程安全,并且init也保证只发生一次-如果两个线程同时调用get(),一个线程将进行计算,另一个线程将阻塞等待 :
final LazyReference=new LazyReference(){
受保护的MyObject create()引发异常{
//在这里做一些有用的对象构造
返回新的MyObject();
}
};
//螺纹1
MyObject MyObject=ref.get();
//螺纹2
MyObject MyObject=ref.get();
使用
公共类CountryLis
final LazyReference<MyObject> ref = new LazyReference() {
protected MyObject create() throws Exception {
// Do some useful object construction here
return new MyObject();
}
};
//thread1
MyObject myObject = ref.get();
//thread2
MyObject myObject = ref.get();
public class CountryList {
private CountryList() {}
private static class CountryListHolder {
static final List<Country> INSTANCE = new List<Country>();
}
public static List<Country> getInstance() {
return CountryListHolder.INSTANCE;
}
...
}
private Supplier<List<String>> supplier = new Supplier<List<String>>(){
public List<String> get(){
return loadCountryList();
}
};
// volatile reference so that changes are published correctly see invalidate()
private volatile Supplier<List<String>> memorized = Suppliers.memoize(supplier);
public List<String> list(){
return memorized.get();
}
public void invalidate(){
memorized = Suppliers.memoize(supplier);
}
public class CountryList {
@GuardedBy("cache")
private final List<String> cache = new ArrayList<String>();
private List<String> loadCountryList() {
// HEAVY OPERATION TO LOAD DATA
}
public List<String> list() {
synchronized (cache) {
if( cache.isEmpty() ) {
cache.addAll(loadCountryList());
}
return Collections.unmodifiableList(cache);
}
}
public void invalidateCache() {
synchronized (cache) {
cache.clear();
}
}
}
public String operationB() {
if (!loaded) {
load();
loaded = true;
}
//Do whatever.
return whatever;
}
public String operationB() {
synchronized(loaded) {
if (!loaded) {
load();
loaded = true;
}
}
//Do whatever.
return whatever;
}
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
@Override
public List<String> list() {
if (cache == null) {
cache = loadCountryList();
}
return cache;
}
@Override
public void invalidateCache() {
cache = null;
}
@Override
public synchronized List<String> list() {
if (cache == null) {
cache = loadCountryList();
}
return cache;
}
@Override
public synchronized void invalidateCache() {
cache = null;
}
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");
}
}