Dal momento che hai riscritto la tua domanda, ecco una nuova risposta .
In primo luogo, sembra a me come l'implementazione di computeNonRecursivelly
è ancora ricorsivo, dal momento getOrEnqueue
lo chiama.
Non penso che sia possibile utilizzare direttamente uno Cache
, perché è necessario disporre di due passaggi nel calcolo: uno che indica le dipendenze per il valore desiderato e uno che esegue il calcolo una volta soddisfatte le dipendenze. Funzionerà solo se non si hanno mai delle dipendenze cicliche, tuttavia (è lo stesso requisito della ricorsione).
In questo modo, è possibile accodare le dipendenze che non sono già nella cache (e le loro dipendenze, ecc.) Quindi calcolarle nell'ordine corretto. Qualcosa sulla falsariga di:
public abstract class TwoStepCacheLoader<K, V> extends CacheLoader<K, V> {
public abstract Set<K> getDependencies(K key);
}
public class TwoStepCache<K, V> extends ForwardingLoadingCache<K, V> {
private final TwoStepCacheLoader<K, V> loader;
private LoadingCache<K, V> cache;
public TwoStepCache(TwoStepCacheLoader<K, V> loader) {
this.loader = loader;
cache = CacheBuilder.newBuilder().build(loader);
}
@Override
public V get(K key)
throws ExecutionException {
V value = cache.getIfPresent(key);
if (value != null) {
return value;
}
Deque<K> toCompute = getDependenciesToCompute(key);
return computeDependencies(toCompute);
}
private Deque<K> getDependenciesToCompute(K key) {
Set<K> seen = Sets.newHashSet(key);
Deque<K> dependencies = new ArrayDeque<K>(seen), toCompute = new ArrayDeque<K>(seen);
do {
for (K dependency : loader.getDependencies(dependencies.remove())) {
if (seen.add(dependency) && // Deduplication in the dependencies
cache.getIfPresent(dependency) == null) {
// We need to compute it.
toCompute.push(dependency);
// We also need its dependencies.
dependencies.add(dependency);
}
}
} while (!dependencies.isEmpty());
return toCompute;
}
private V computeDependencies(Deque<K> toCompute)
throws ExecutionException {
V value;
do {
value = cache.get(toCompute.pop());
} while (!toCompute.isEmpty());
// The last computed value is for our key.
return value;
}
@Override
public V getUnchecked(K key) {
try {
return get(key);
} catch (ExecutionException e) {
throw new UncheckedExecutionException(e.getCause());
}
}
@Override
protected LoadingCache<K, V> delegate() {
return cache;
}
}
Ora è possibile implementare un TwoStepCacheLoader
che chiama la cache in modo sicuro:
public class Fibonacci {
private LoadingCache<Integer, Integer> cache = new TwoStepCache<Integer, Integer>(new FibonacciCacheLoader());
public int fibonacci(int n) {
return cache.getUnchecked(n);
}
private class FibonacciCacheLoader extends TwoStepCacheLoader<Integer, Integer> {
@Override
public Set<Integer> getDependencies(Integer key) {
if (key <= 1) {
return ImmutableSet.of();
}
return ImmutableSet.of(key - 2, key - 1);
}
@Override
public Integer load(Integer key)
throws Exception {
if (key <= 1) {
return 1;
}
return cache.get(key - 2) + cache.get(key - 1);
}
}
}
Ho eseguito un test di unità su di esso, sembra funzionare correttamente.
Avete guardato l'articolo del Dr. Heinz M. Kabutz su [Fork/Join With Fibonacci and Karatsuba] (http://www.javaspecialists.eu/archive/Issue201.html). Alcune delle sue idee potrebbero essere applicate. – OldCurmudgeon
@OldCurmudgeon: Ora ce l'ho, è interessante ma non mi è stato di grande aiuto. – maaartinus
Ho trovato questo [esempio di cache Fibonacci] (http://vladmihalcea.com/2014/03/03/caching-best-practices/) che sembra applicarsi. Vedi la sezione chiamata "Tempo di gioco" verso il basso. Sembra funzionare mantenendo il numero precedente calcolato nella cache e sfrattando i vecchi valori. Il carico ricorsivo è evitato da un attento ordinamento delle istruzioni nel metodo 'load'.Non sono sicuro che sia possibile estenderlo oltre l'esempio del 'giocattolo', come dici tu. –