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

Igo*_*hin 37 java caching lazy-loading

假设我们的应用程序中有一个CountryList对象,它应返回国家/地区列表.加载国家是一项繁重的操作,因此应该缓存该列表.

其他要求:

  • CountryList应该是线程安全的
  • CountryList应加载延迟(仅按需)
  • 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

你怎么看待这件事?你觉得它有什么坏处吗?还有其他办法吗?我怎样才能让它变得更好?我应该在这种情况下寻找另一种解决方案吗?

谢谢.

Gar*_*vis 33

谷歌收藏实际上只提供这类东西:供应商

你的代码是这样的:

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);
}
Run Code Online (Sandbox Code Playgroud)


Igo*_*hin 17

谢谢你们所有人,尤其是那些提出这个想法的用户" gid ".

我的目标是优化get()操作的性能,因为invalidate()操作将被称为非常罕见.

我写了一个测试类,它启动了16个线程,每个线程调用get() - 操作一百万次.通过这个课程,我在我的2核机器上描述了一些实现.

测试结果

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
Run Code Online (Sandbox Code Playgroud)

1)"无同步"不是线程安全的,但为我们提供了可以比较的最佳性能.

@Override
public List<String> list() {
    if (cache == null) {
        cache = loadCountryList();
    }
    return cache;
}

@Override
public void invalidateCache() {
    cache = null;
}
Run Code Online (Sandbox Code Playgroud)

2)"正常同步" - 相当不错的性能,标准的无需实施

@Override
public synchronized List<String> list() {
    if (cache == null) {
        cache = loadCountryList();
    }
    return cache;
}

@Override
public synchronized void invalidateCache() {
    cache = null;
}
Run Code Online (Sandbox Code Playgroud)

3)"与MapMaker" - 性能非常差.

请在顶部查看我的问题代码.

4)"with Suppliers.memoize" - 良好的表现.但由于性能相同"正常同步",我们需要对其进行优化或仅使用"正常同步".

有关代码,请参阅用户"gid"的答案.

5)"具有优化的memoize" - 性能与"无同步" - 实现相当,但是线程安全的.这是我们需要的.

缓存类本身:(此处使用的供应商界面来自Google Collections Library,它只有一个方法get().请参阅http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/ common/base/Supplier.html)

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用示例:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)


Mik*_*ike 5

每当我需要缓存某些东西时,我都喜欢使用代理模式.使用这种模式可以解决问题.您的原始对象可能与延迟加载有关.您的代理(或监护人)对象可以负责验证缓存.

详细地:

  • 定义一个对象的CountryList类,它是线程安全的,最好使用同步块或其他信号量锁.
  • 将此类的接口解压缩到CountryQueryable接口.
  • 定义另一个实现CountryQueryable的对象CountryListProxy.
  • 仅允许实例化CountryListProxy,并且仅允许通过其接口引用它.

从这里,您可以将缓存失效策略插入代理对象.保存上次加载的时间,并在下次查看数据的请求时,将当前时间与缓存时间进行比较.定义容差级别,如果时间过长,则重新加载数据.

至于Lazy Load,请参阅此处.

现在为一些好的家庭示例代码:

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.
    }

}
Run Code Online (Sandbox Code Playgroud)