I've been going through the Java Concurrency in Practice book. It is definitively a great reference. I'm trying to extend the last example of the efficient and scalable result set cache to incorporate soft references. The objects stored in my cache can grow rather large and I need some way for those objects to get garbage collected when memory is low hence the soft references. Getting the thread safe and soft reference concepts to work has tested my limits and I need help. So far this is what I have working. I'm not sure if this is really thread safe. Could anyone please take a look at it and comment?
public class RelationCollectionCache {
// properties
private final ConcurrentMap<Integer, Reference<Future<RelationCollection>>> cache =
new ConcurrentHashMap<Integer, Reference<Future<RelationCollection>>>();
private final ReferenceQueue<? super Future<RelationCollection>> queue =
new ReferenceQueue<Future<RelationCollection>>();
// constructors
public RelationCollectionCache() {
}
// methods
public RelationCollection load(final Integer id) {
Reference<Future<RelationCollection>> reference = cache.get(id);
Future<RelationCollection> future = (reference == null) ? null : reference.get();
if (future == null) {
Callable<RelationCollection> eval = new Callable<RelationCollection>() {
public RelationCollection call() throws Exception {
return compute(id);
}
};
FutureTask<RelationCollection> task = new FutureTask<RelationCollection>(eval);
reference = cache.putIfAbsent(id, new InternalSoftReference(id, task));
if (((reference == null) ? null : reference.get()) == null) {
future = task;
task.run();
}
}
try {
return future.get();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
private RelationCollection compute(Integer id) {
RelationCollection collection = new RelationCollection();
// lengthy computation of a large collection
return collection;
}
public RelationCollection get(Integer id) {
clean();
Reference<Future<RelationCollection>> reference = cache.get(id);
Future<RelationCollection> future = (reference == null) ? null : reference.get();
if (future == null)
return null;
try {
return future.get();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
public void remove(Integer id) {
clean();
cache.remove(id);
}
public void clean() {
InternalSoftReference reference = (InternalSoftReference) queue.poll();
while (reference != null) {
cache.remove(reference.id);
reference = (InternalSoftReference) queue.poll();
}
}
// internal classes
private class InternalSoftReference extends SoftReference<Future<RelationCollection>> {
private Integer id;
public InternalSoftReference(Integer id, Future<RelationCollection> future) {
super(future, queue);
this.id = id;
}
}
}
This implementation calls the clean method at every operation to get rid of garbage collected references. This could also be a Thread but that incorporates another level of concurrency which I haven't even explored.