简体   繁体   中英

Concurrent map with weak keys

I have a highly concurrent application which makes use of resources on the file system. Chances that two threads will access the same resource at the same time are rather small but if this would happen the application would likely show wired behavior.

Each resource can be mapped by a vector of String coordinates (bundled in a class ResourceIdentifier ). In my current solution,I created a ConcurrentMap of such resource identifiers to collect monitors that are used by the threads when they access a resource: ( ResourceIdentifier overrides equals and hashCode correctly.)

ConcurrentMap<ResourceIdentifier, ResourceIdentifier> concurrentMap 
   = new ConcurrentHashMap<>();

public Object aquireMonitor(ResourceIdentifier resourceIdentifier) {
  concurrentMap.putIfAbsent(resourceIdentifier, resourceIdentifier);
  return concurrentMap.get(resourceIdentifier);
}

When the resource is accessed, I synchronize the access on the monitor object which is returned by aquireMonitor . As far as I understood the implementation of ConcurrentHashMap , this does not neccessarily block all threads (I read this blog article in order to understand the implementation.) and my application can happily run without the danger of concurrent access to one of the resources which formerly introduced ugly bugs on very rare occasions.

However: My application manages a big number of resources and the concurrentMap grows with runtime. This is why I now try to add weak reference semantics to my application (by using Guava):

ConcurrentMap<ResourceIdentifier, ResourceIdentifier> concurrentMap 
   = new MapBuilder().weakValues().weakKeys()
     .concurrencyLevel(CONCURRENCY_LEVEL).makeMap();

public Object aquireMonitor(ResourceIdentifier resourceIdentifier) {
  ResourceIdentifier monitor;
  do {
    concurrentMap.putIfAbsent(resourceIdentifier, resourceIdentifier);
    monitor = concurrentMap.get(resourceIdentifier);
  } while(monitor == null);
  return monitor;
}

CONCURRENCY_LEVEL is of course a static field.

My thought was like this: Whenever a monitor is still in use by another thread, it will of course hold a (strong) reference to this monitor. Therefore, the entry in the ConcurrentMap will not be garbage collected and it is guaranteed that a monitor is shared when two threads want to access the same resource. (The loop addresses possible garbage collection between the calls of putIfAbsent and get .)

However, MapMaker.weakKeys breaks the contract that entries are found by equals and uses identity instead.

Now I wonder: Does anybody know where to go from here? Or is this approach a bad idea anyways? And as a side question: Would the whole entry be removed from the map if I only used weakValues ? Or would the map always have another strong reference by its keys? Thanks for help!

PS: My first guess is that I should migrate away from a map to a cache. Is this maybe the best solution? I never used Guava before, but for now I found the same restriction on key comparison for caches.

PPS: I cannot create locks on the file system. (Not my call.)

You will need weak references for both keys and values.

I recommend that you switch to a cache, or barring that, that you switch to a ConcurrentMap with SoftReferences - the GC is eager about collecting weak references and so they're not really appropriate for a cache, whereas it delays the collection of soft references while still not allowing them to cause an OutOfMemoryError . To implement a soft reference concurrent map you would create a ConcurrentMap wrapper, eg

class SoftConcurrentMap<K, V> extends ConcurrentHashMap<SoftReference<K>, SoftReference<V>> {
    ConcurrentHashMap<SoftReference<K>, SoftReference<V>> map = new ConcurrentHashMap<>();

    V public void get(Object key) {
        SoftReference<V> value = map.get(new SoftRefrence(key));
        if(value != null && value.get() != null) {
            return value.get();
        } else {
            map.remove(new SoftReference(key));
            return null;
        }
    }

    V put(K key, V value) {
        SoftReference<V> oldValue = map.put(new SoftReference(key), new SoftReference(value));
        return oldValue == null ? null : oldValue.get();
    }
}

And so on. This is a lot of method wrapping through, so I recommend you use something like EHCache instead.

I found another neat solution which I actually implemented. Maybe it was too easy to just think of it in the beginning:

ConcurrentMap<ResourceIdentifier, ResourceIdentifier> concurrentMap 
   = new MapBuilder().weakValues()
     .concurrencyLevel(CONCURRENCY_LEVEL).makeMap();

public Object aquireMonitor(ResourceIdentifier resourceIdentifier) {
  ResourceIdentifier monitor;
  do {
    concurrentMap.putIfAbsent(resourceIdentifier, new Object());
    monitor = concurrentMap.get(resourceIdentifier);
  } while(monitor == null);
  return monitor;
}

while not using weakKeys . This works like a charm. The keys do no longer represent a strong reference to the actual object which is used as a monitor and the map's entries get garbage collected whenever no thread holds a strong reference to them anymore.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM