Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.)

like image 786
Rafael Winterhalter Avatar asked Jul 01 '13 10:07

Rafael Winterhalter


People also ask

Is ConcurrentHashMap slower than HashMap?

HashMap, Hashtable, ConcurrentHashMap performance comparison If you notice ConcurrentHashMap is slightly slower performing than HashMap, however it's a 100% thread safe implementation. On the other hand Hashtable is also thread safe implementation, but it's 18 times slower than HashMap for this test scenario.

Why is null not allowed in ConcurrentHashMap?

The main reason that nulls aren't allowed in ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) is that ambiguities that may be just barely tolerable in non-concurrent maps can't be accommodated.

Is HashMap faster than ConcurrentHashMap?

HashMap performance is relatively high because it is non-synchronized in nature and any number of threads can perform simultaneously. But ConcurrentHashMap performance is low sometimes because sometimes Threads are required to wait on ConcurrentHashMap.

Can ConcurrentHashMap have duplicate keys?

Summary of ConcurrentHashMap features – It does not allow duplicate keys. – It does not allow null to be used as a key or value.


1 Answers

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, e.g.

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.

like image 72
Zim-Zam O'Pootertoot Avatar answered Oct 05 '22 21:10

Zim-Zam O'Pootertoot