0

I'm using the MapMaker to implement caching of data objects in my application:

public class DataObjectCache<DO extends MyDataObject> {

    private final ConcurrentMap<String, DO> innerCache;

    public DataObjectCache(Class<DO> doClass) {

        Function<String, DO> loadFunction = new Function<String, DO>() {
            @Override
            public DO apply(String id) {
                //load and return DO instance
            }
        };

        innerCache = new MapMaker()
             .softValues()
             .makeComputingMap(loadFunction);
    }

    private DO getDataObject(String id) {
        return innerCache.get(id);
    }

    private void putDataObject(DO dataObject) {
        innerCache.putIfAbsent(dataObject.getID(), dataObject);
    }
}

One of these DataObjectCaches would be instantiated for each data object class, and they would be kept in a master Map, using the Class objects as keys.

There's a minority of data object classes whose instances I don't want cached. However I would still like them to be instantiated by the same code, which the Function is calling, and would still need concurrency in regard to loading them distinctly.

In these cases, I'm wondering if I can just set the maximum size of the map to 0, so that entries are evicted immediately, but still take advantage of the atomic computing aspects of the map. Is this a good idea? Inefficient?

EDIT:

I realized that if I evicted entries immediately after loading them, there's no way to guarantee they are distinctly loaded - if the Map isn't keeping track of them, multiple instances of an object with the same ID could be floating around the environment. So instead of doing this, I think I'll use weak values instead of soft values for the types of objects I don't want taking up cache - let me know if anyone has an opinion on this.

Paul Bellora
  • 54,340
  • 18
  • 130
  • 181

2 Answers2

2

In light of your edit, it sounds like what you're looking for is an interner. An interner returns a representative instance; the same object will be returned by Interner.intern for all objects that are equal according to your equals method. From the Javadoc:

Chooses and returns the representative instance for any of a collection of instances that are equal to each other. If two equal inputs are given to this method, both calls will return the same instance. That is, intern(a).equals(a) always holds, and intern(a) == intern(b) if and only if a.equals(b). Note that intern(a) is permitted to return one instance now and a different instance later if the original interned instance was garbage-collected.

See http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/collect/Interner.html and http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/collect/Interners.html

That said, it depends what you mean when you say you don't want it cached. If you truly want to return a fresh instance every time, then you'd have to have multiple instances of equivalent objects "floating around".

Interning is holding on to an instance (so it can return the same one), so it is still sort of a cache. I would want to know why you want to avoid caching. If it is because of the size of the objects, you can use a weak interner; the instance will be available for GC when it's no longer referenced. Then again, simply using a MapMaker map with weak values would accomplish that as well.

If, on the other hand, the reason you don't want to cache is because your data is liable to change, interning could be your answer. I would imagine what you'd want is to retrieve the object every time, and then intern it. If the object is equal to the cached one, the interner would simply return the existing instance. If it is different, the interner would cache the new one. Your responsibility then would be to write an equals method on your object that meets the requirements for using a new vs interned instance.

Ray
  • 4,829
  • 4
  • 28
  • 55
  • Oh that's interesting. So you're suggesting I use an Interner for the types of data objects that I don't want cached if I understand correctly (the Maps would still be used for cached types). And I'm guessing this would be the weak Interner, right? – Paul Bellora Jun 09 '11 at 16:28
  • I've added to my answer to try to respond to your comment. Let me know if you need more clarification – Ray Jun 09 '11 at 16:50
  • Thanks for helping me out with this. You're right to make the distinction that an Interner would be a kind of cache. When I say there are types of objects I don't want cached, I just mean that I know these objects are typically one-time-use and there's no point in holding onto them any longer than they're referenced. I think I'll stick to the Map for all types and just use weak values instead of soft for these one-use types. Thanks for teaching me about Interners +1 – Paul Bellora Jun 09 '11 at 17:29
0

Well, MapMaker.maximumSize has this line: checkArgument(size > 0, "maximum size must be positive"), which will make this impossible. The expireAfter methods also require positive arguments. It's pretty clear that the API designers didn't want you to use their MapMaker-made maps this way.

That said, I suppose, if you really want to use the cache as a passthrough, you could use an expireAfterWrite of 1 nanosecond. It's a hack, but it would practically have the same effect.

Ray
  • 4,829
  • 4
  • 28
  • 55
  • Actually, looks like this was changed in release 09, and is now specifically called out as something you can do: "When size is zero, elements can be successfully added to the map, but are evicted immediately." – Ray Jun 09 '11 at 13:21
  • yeah, I considered this approach after seeing that. But I realized it won't fit my requirements, see my edit above. – Paul Bellora Jun 09 '11 at 14:48