Dank Sie alle Jungs, vor allem für Benutzer " gid "wer gab die Idee.
Mein Ziel war es, die Leistung für die Operation get() zu optimieren, da die Operation invalidate() sehr selten genannt wird.
Ich schrieb eine Test-Klasse, die 16 Threads startet, jeder ruft Get() - Operation eine Million Mal. Mit dieser Klasse habe ich eine Implementierung auf meiner 2-Kern-Maschine beschrieben.
Prüfergebnisse
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) „Keine Synchronisation“ ist nicht Thread-sicher, sondern gibt uns die beste Leistung, die wir vergleichen können.
@Override
public List<String> list() {
if (cache == null) {
cache = loadCountryList();
}
return cache;
}
@Override
public void invalidateCache() {
cache = null;
}
2) "Normale Synchronisation" - ziemlich gut Performace, Standard no-brainer Implementierung
@Override
public synchronized List<String> list() {
if (cache == null) {
cache = loadCountryList();
}
return cache;
}
@Override
public synchronized void invalidateCache() {
cache = null;
}
3) "mit Map Maker" - sehr schlechte Leistung.
Siehe meine Frage an der Spitze für den Code.
4) "mit Suppliers.memoize" - gute Leistung. Aber da die Performance die gleiche "normale Synchronisation" hat, müssen wir sie optimieren oder einfach die "normale Synchronisation" verwenden.
Siehe die Antwort des Benutzers "GID" für Code.
5) "mit optimierten Memoize" - die Leistung vergleichbar mit "keine Synchronisierung" -Einführung, aber Thread-Safe. Das ist die eine, die wir brauchen.
Die Cache-Klasse selbst: (. Der Lieferant Schnittstellen hier ist von Google Sammlungen Bibliothek und es hat nur eine Methode get() sieht 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;
}
}
}
Beispiel für die Verwendung:
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");
}
}
Ich bin nicht davon überzeugt, dass dies Thread-sicher ist. Was passiert, wenn zwei Threads invalidateCache() gleichzeitig aufrufen oder einer die list() gleichzeitig aufruft, während ein anderer invalidateCache() aufruft? – ChrisH
MapMaker gibt eine thread-sichere Implementierung der Map-Schnittstelle zurück. makeComputingMap() führt die Berechnung atomar aus (http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/collect/MapMaker.html#makeComputingMap(com.google.common.base.Function)).) –
Möchten Sie eine Antwort akzeptieren? –