I\'m implementing a cache for Objects stored persistently. The idea is:
getObjectFromPersistence(long id); ///Takes about 3 seconds
You can use the Guava MapMaker for this:
ConcurrentMap<Long, CustomObject> graphs = new MapMaker()
.weakValues()
.makeMap();
You can even include the computation part by replacing makeMap()
with this:
.makeComputingMap(
new Function<Long, CustomObject>() {
public CustomObject apply(Long id) {
return getObjectFromPersistence(id);
}
});
Since what you are writing looks a lot like a cache, the newer, more specialized Cache (built via a CacheBuilder) might be even more relevant to you. It doesn't implement the Map
interface directly, but provides even more controls that you might want for a cache.
You can refer to this for a detailed how to work for CacheBuilder and here is an example for fast access:
LoadingCache<Integer, String> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(
new CacheLoader<Integer, String>() {
@Override
public String load(Integer id) throws Exception {
return "value";
}
}
);
Have you tried android.util.LruCache (its a SDK11 class but it's also in the compatibility package as android.support.v4.util.LruCache). It does not implement java.util.Map
but works like a Map and you can define how much memory will it take and it will flush old (unused cached objects by itself).
You could start a "cleanup" - Thread every once in a while. Perhaps if your map size exceeds a threshold but at most every 5 minutes .... something like that.
Keep the cleanup cycles short to not block the main functionality.
I had the need to store tagged weak objects and figured instead of using WeakHashMap<String, T>
, I could just use WeakHashMap<T, String>
instead.
This is Kotlin, but should apply to Java equally:
abstract class InstanceFactory<T> {
@Volatile
private var instances: MutableMap<T, String> = WeakHashMap<T, String>()
protected fun getOrCreate(tag: String = SINGLETON, creator: () -> T): T =
findByTag(tag)?.let {
it
} ?: synchronized(this) {
findByTag(tag)?.let {
it
} ?: run {
creator().also {
instances[it] = tag
}
}
}
private fun findByTag(tag: String): T? = instances.entries.find { it.value == tag }?.key
companion object {
const val SINGLETON = "singleton"
}
}
This can be used as follows:
class Thing(private val dependency: Dep) { ... }
class ThingFactory(private val dependency: Dep) : InstanceFactory<Thing>() {
createInstance(tag: String): Thing = getOrCreate(tag) { Thing(dependency) }
}
Simple singletons can be done like this:
object ThingFactory {
getInstance(dependency: Dependency): Thing = getOrCreate { Thing(dependency) }
}
A WeakReference
is added to its ReferenceQueue supplied at the construction time when its reference is collected.
You could poll
the ReferenceQueue
whenever you access the cache, and hold a HashMap<WeakReference<CustomObject>,Long>
to know which entry to remove if a reference is found in the queue.
Alternatively, if the cache is not frequently used, you can watch the queue in a separate thread.
I think the best option (if a dependency on Guava is undesirable) would be to use a custom subclass of WeakReference that remembers its ID, so that your cleanup thread can remove the weak values during cleanup of the WeakReferences.
The implementation of the weak reference, with the necessary ReferenceQueue and cleanup thread would look something like this:
class CustomObjectAccess {
private static final ReferenceQueue<CustomObject> releasedCustomObjects =
new ReferenceQueue<>();
static {
Thread cleanupThread = new Thread("CustomObject cleanup thread")
while (true) {
CustomObjectWeakReference freed = (CustomObjectWeakReference)
CustomObjectWeakReference.releasedCustomObjects.remove();
cache.remove(freed.id);
}
};
cleanupThread.start();
}
private Map<CustomObjectID, CustomObjectWeakReference> cache;
public CustomObject get(CustomObjectID id) {
synchronized(this){
CustomObject result= getFromCache(id);
if (result==null) {
result=getObjectFromPersistence(id);
addToCache(result);
}
}
return result;
}
private addToCache(CustomObject co) {
cache.put(CustomObject.getID(), new CustomObjectWeakReference(co));
}
private getFromCache(CustomObjectID id) {
WeakReference<CustomObject> weak = cache.get(id);
if (weak != null) {
return weak.get();
}
return null;
}
class CustomObjectWeakReference extends WeakReference<CustomObject> {
private final CustomObjectID id;
CustomObjectWeakReference(CustomObject co) {
super(co, releasedCustomObjects);
this.id = co.getID();
}
}
}