Gracias usted todos los chicos, especialmente a los usuarios " gid "quién dio la idea.
Mi objetivo era optimizar el rendimiento de la operación get() teniendo en cuenta que la operación invalidate() se llamará muy rara.
Escribí una clase de prueba que comienza con 16 hilos, cada uno llamando a get() - Operación un millón de veces. Con esta clase, realicé un perfil de implementación en mi maschine de 2 núcleos.
resultados de las pruebas
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) "No hay sincronización" no es seguro para subprocesos, pero nos da el mejor rendimiento que podemos comparar.
@Override
public List<String> list() {
if (cache == null) {
cache = loadCountryList();
}
return cache;
}
@Override
public void invalidateCache() {
cache = null;
}
2) "La sincronización normal" - bastante buena performace, estándar de aplicación obviedad
@Override
public synchronized List<String> list() {
if (cache == null) {
cache = loadCountryList();
}
return cache;
}
@Override
public synchronized void invalidateCache() {
cache = null;
}
3) "con Map Maker" - un rendimiento muy pobre.
Consulte mi pregunta en la parte superior para obtener el código.
4) "con Suppliers.memoize" - buen rendimiento. Pero como el rendimiento es la misma "sincronización normal", debemos optimizarlo o simplemente usar la "sincronización normal".
Consulte la respuesta del usuario "gid" para ver el código.
5) "con memoize optimizado" - el rendimiento es comparable al de "sin sincronización": implementación, pero seguro para subprocesos. Este es el que necesitamos.
La misma clase de caché: (. Las interfaces Proveedor ha utilizado aquí es de Google colecciones de bibliotecas y tiene sólo un método get() ver 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;
}
}
}
Ejemplo del uso:
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");
}
}
No estoy seguro de que esto sea seguro para subprocesos. ¿Qué pasa si dos hilos llaman invalidateCache() simultáneamente o uno está llamando a list() al mismo tiempo que otro llama a invalidateCache()? – ChrisH
MapMaker devuelve una implementación thread-safe de la interfaz de Map. makeComputingMap() realiza el cálculo de forma atómica (http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/collect/MapMaker.html#makeComputingMap(com.google.common.base.Function)) –
¿quieres aceptar una respuesta? –