java中一个对象的线程安全缓存

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应该是线程安全的
  • 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");
    }
}