问题
I'm implementing a cache for Objects stored persistently. The idea is:
- Method
getObjectFromPersistence(long id); ///Takes about 3 seconds
- Method
getObjectFromCache(long id) //Instantly
And have a method: getObject(long id)
with the following pseudocode:
synchronized(this){
CustomObject result= getObjectFromCache(id)
if (result==null){
result=getObjectFromPersistence(id);
addToCache(result);
}
return result;
}
But I need to allow the CustomObject to be collected by the garbage collector. Until now I was using an HashMap<Long,WeakReference<CustomObject>
for the implementation. The problem is that over the time the HashMap becomes filled of empty WeakReferences
.
I've checked WeakHashMap but there the keys are weak (and the values are still strong references) so having the longs with WeakReferences have no sense.
Whats the best solution for solving this problem? Is there some "inverse WeakHashMap" or something similar?
Thanks
回答1:
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";
}
}
);
回答2:
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.
回答3:
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).
回答4:
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.
回答5:
You can also test WeakValueHashMap from jboss-common http://docs.jboss.org/jbossas/javadoc/4.0.2/org/jboss/util/collection/WeakValueHashMap.java.html
回答6:
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();
}
}
}
回答7:
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) }
}
来源:https://stackoverflow.com/questions/13413272/hashmap-with-weak-values