Reputation: 181
(please note i cannot use external libraries for the cache).
---may it can be done using the stream API? ---
i need to implement a cache, it's has 1 key property:
If the cache is asked for a key that it doesn't contain, it should fetch the data using an externally provided function that reads the data from another source (database or similar).
i've started to create a basic skelaton code:
public interface ICache<K,V> {
}
interface IDataSource<K,V> {
void put(K key, V value);
V get(K key);
}
public class Cache<K,V> implements ICache<K,V> {
Map<K,V> cache = new HashMap<>();
IDataSource<K,V> dataSource;
public Cache(IDataSource<K,V> dataSrc) {
dataSource = dataSrc;
}
//may it change to a future? how it can be done?
public V getAsync(K key) {
if (cache.containsKey(key)) {
return cache.get(key);
}
else {
//do some async op
}
}
}
can you advice?
do u think it's need more features?
Upvotes: 0
Views: 1154
Reputation: 22158
In reality what you are writing is a lazy evaluator. You are providing a Supplier
for the value, without computing it the first time. The moment someone asks for your value you compute it and return it, memoizing (caching) it for future use.
Have a look at Vavr's Lazy
class, it is doing precisely this (but for a single value). You can take some ideas from what that is doing, and also some extra utility methods like checking if it was already computed.
https://github.com/vavr-io/vavr/blob/master/vavr/src/main/java/io/vavr/Lazy.java
Another option is to simply use ConcurrentHashMap
. It provides methods to safely (atomically) update values if they are not in the map.
If you want it to be asynchronous you need to introduce some ExecutorService
or use CompletableFuture
(with your own ExecutorService
or the default thread pool that is used by parallel streams etc.). For example:
public class Cache<K,V> implements ICache<K,V> {
Map<K,V> cache = new ConcurrentHashMap<>();
IDataSource<K,V> dataSource;
public Cache(IDataSource<K,V> dataSrc) {
dataSource = dataSrc;
}
// async non-blocking call
public CompletableFuture<V> getAsync(K key) {
return CompletableFuture.supplyAsync(() -> get(key));
}
// blocking call
public V get(K key) {
//computeIfAbsent is atomic and threadsafe, in case multiple CompletableFutures try this in parallel
return cache.computeIfAbsent(key, (k) -> dataSource.get(k));
}
}
If you also wanted to have an async direct cache and datasource update you could do something like:
public CompletableFuture<Void> putAsync(K key, V value) {
return CompletableFuture.runAsync(() -> {
synchronized (cache) {
dataSource.put(key, value);
cache.put(key, value);
}
}
}
Although honestly I would avoid having 2 entrypoints to update the dataSource
(the cache and the dataSource directly). Also it is difficult to make this completely thread safe without having the synchronized
(which blocks concurrent cache puts completely from happening in parallel, even if the key is different).
Upvotes: 2