I am looking for a high-performance, concurrent, MultiMap. I have searched everywhere but I simply cannot find a solution that uses the same approach as ConcurrentHashMap (Only locking a segment of the hash array).
The multimap will be both read, added to and removed from often.
The multimap key will be a String and it's value will be arbitrary.
I need O(1) to find all values for a given key, O(N) is OK for removal, but O(logN) would be preferred.
It is crucial that removal of the last value for a given key will remove the container of values from the key, as to not leak memory.
EDIT: HERE'S THE SOLUTION I BUILT, available under ApacheV2: Index (multimap)
-
7There's no Map with O(1) lookup (except the bucket algorithm stuff, as usual). HashMaps have O(cn) with very small c.ziggystar– ziggystar2010年09月03日 12:43:25 +00:00Commented Sep 3, 2010 at 12:43
-
2ziggystar, it depends on how well the hashing function distributes the keys. If it does so randomly - which you could expect of a modern hash, for arbitrary strings - then lookup is O(1). This is also what the HashMap Javadoc says.Thomas Kappler– Thomas Kappler2010年09月03日 12:59:12 +00:00Commented Sep 3, 2010 at 12:59
-
2I have a registry of potentially millions of objects and they may share some properties, and I need an index for said properties, so I can retrieve all objects having a certain property.Viktor Klang– Viktor Klang2010年09月03日 14:02:18 +00:00Commented Sep 3, 2010 at 14:02
-
1Perhaps it is time to search for your answer on cstheory.stackexchange.com? It looks like you'll be rolling your own data structure...Daniel C. Sobral– Daniel C. Sobral2010年09月03日 18:46:22 +00:00Commented Sep 3, 2010 at 18:46
-
2@John V: Here's what I built: gist.github.com/566793Viktor Klang– Viktor Klang2010年09月06日 13:16:12 +00:00Commented Sep 6, 2010 at 13:16
10 Answers 10
Why not wrap ConcurrentHashMap[T,ConcurrentLinkedQueue[U]] with some nice Scala-like methods (e.g. implicit conversion to Iterable or whatever it is that you need, and an update method)?
6 Comments
map.get(key).remove(value)
should do the trick as long as you leave the empty queue there as a placeholder when you remove a key and catch the exception if it's not there (including the NPE off of get). If you can't leave the queue as a placeholder, then it's difficult to ensure safety unless you do lock the entire map while you clean out the cruft.akka.util.ConcurrentMultiMap
. Thanks a lot, Viktor!Have you tried Google Collections? They have various Multimap implementations.
3 Comments
There is one in akka although I haven't used it.
Comments
I had a requirement where I had to have a Map<Comparable, Set<Comparable>>
where insertion on the Map be concurrent and also on the corresponding Set, but once a Key was consumed from the Map, it had to be deleted, think if as a Job running every two seconds which is consuming the whole Set<Comparable>
from an specific Key but insertion be totally concurrent so that most values be buffered when the Job kicks in, here is my implementation:
Note: I use Guava's helper class Maps to create the concurrent Maps, also, this solution emulates Java concurrency in Practice Listing 5.19:
import com.google.common.collect.MapMaker;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
/**
* A general purpose Multimap implementation for delayed processing and concurrent insertion/deletes.
*
* @param <K> A comparable Key
* @param <V> A comparable Value
*/
public class ConcurrentMultiMap<K extends Comparable, V extends Comparable>
{
private final int size;
private final ConcurrentMap<K, Set<V>> cache;
private final ConcurrentMap<K, Object> locks;
public ConcurrentMultiMap()
{
this(32, 2);
}
public ConcurrentMultiMap(final int concurrencyLevel)
{
this(concurrencyLevel, 2);
}
public ConcurrentMultiMap(final int concurrencyLevel, final int factor)
{
size=concurrencyLevel * factor;
cache=new MapMaker().concurrencyLevel(concurrencyLevel).initialCapacity(concurrencyLevel).makeMap();
locks=new MapMaker().concurrencyLevel(concurrencyLevel).initialCapacity(concurrencyLevel).weakKeys().weakValues().makeMap();
}
private Object getLock(final K key){
final Object object=new Object();
Object lock=locks.putIfAbsent(key, object);
if(lock == null){
lock=object;
}
return lock;
}
public void put(final K key, final V value)
{
synchronized(getLock(key)){
Set<V> set=cache.get(key);
if(set == null){
set=Sets.newHashSetWithExpectedSize(size);
cache.put(key, set);
}
set.add(value);
}
}
public void putAll(final K key, final Collection<V> values)
{
synchronized(getLock(key)){
Set<V> set=cache.get(key);
if(set == null){
set=Sets.newHashSetWithExpectedSize(size);
cache.put(key, set);
}
set.addAll(values);
}
}
public Set<V> remove(final K key)
{
synchronized(getLock(key)){
return cache.remove(key);
}
}
public Set<K> getKeySet()
{
return cache.keySet();
}
public int size()
{
return cache.size();
}
}
Comments
I made a ConcurrentMultiMap mixin which extends the mutable.MultiMap mixin and has a concurrent.Map[A, Set[B]] self type. It locks per key, which has O(n) space complexity, but its time complexity is pretty good, if you aren't particularly write-heavy.
Comments
3 Comments
It's late for the discussion, yet...
When it comes to high performance concurrent stuff, one should be prepared to code the solution. With Concurrent the statement the Devil is in the details has a complete meaning. It's possible to implement the structure fully concurrent and lock-free.
Starting base would be the NonBlocking Hashtable http://sourceforge.net/projects/high-scale-lib/ and then depending how many values per key and how often need to add/remove some copy on write Object[] for values or an array based Set with semaphore/spin lock.
Comments
I am a bit late on this topic but I think, nowadays, you can use Guava like this:
Multimaps.newSetMultimap(new ConcurrentHashMap<>(), ConcurrentHashMap::newKeySet)
2 Comments
Multimaps.newSetMultimap(...)
support concurrent updates? According to the Javadoc, "The multimap is not threadsafe when any concurrent operations update the multimap, even if map and the instances generated by factory are." cf. google.github.io/guava/releases/23.0/api/docs/com/google/common/… Use MultiMaps from Gauava.
Multimaps.synchronizedMultimap(HashMultimap.create())
1 Comment
synchronized
. Note what the OP was looking for.Have you taken a look to Javalution which is intended for Real time etc. and of course high performance.