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.)
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.
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.
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.
Summary of ConcurrentHashMap features – It does not allow duplicate keys. – It does not allow null to be used as a key or value.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With