Java ConcurrentMap
Jakob Jenkov |
java.util.concurrent.ConcurrentMap
The java.util.concurrent.ConcurrentMap
interface represents a Java Map
which is capable of handling concurrent access (puts and gets) to it.
The ConcurrentMap
has a few extra atomic methods in addition to the methods it inherits from
its superinterface, java.util.Map
.
Java ConcurrentMap Tutorial Video
If you prefer video, I have a Java ConcurrentMap tutorial videre available here:
ConcurrentMap Implementations
Since ConcurrentMap
is an interface, you need to use one of its implementations
in order to use it. The java.util.concurrent
package contains the following implementations
of the ConcurrentMap
interface:
- ConcurrentHashMap
ConcurrentHashMap
The ConcurrentHashMap
is very similar to the java.util.HashTable
class, except
that ConcurrentHashMap
offers better concurrency than HashTable
does.
ConcurrentHashMap
does not lock the Map
while you are reading from it. Additionally,
ConcurrentHashMap
does not lock the entire Map
when writing to it. It only
locks the part of the Map
that is being written to, internally.
Another difference is that ConcurrentHashMap
does not throw ConcurrentModificationException
if the ConcurrentHashMap
is changed while being iterated. The Iterator
is not designed
to be used by more than one thread though.
ConcurrentMap Example
Here is an example of how to use the ConcurrentMap
interface. The example uses
a ConcurrentHashMap
implementation:
ConcurrentMap concurrentMap = new ConcurrentHashMap(); concurrentMap.put("key", "value"); Object value = concurrentMap.get("key");
Avoiding Slipped Conditions
Even though the methods of a ConcurrentHashMap are thread safe - you can still run into concurrency problems if you using it the "wrong way".
Here, I will show you how using a ConcurrentHashMap in the "wrong way" can lead to Slipped Conditions . Look at this Java code example:
ConcurrentMap map = new ConcurrentHashMap(); if( !map.containsKey("key1") ) { map.put("key1", "value1"); }
Even if both the containsKey() and put() method are both thread safe - the above if-statement construct is not thread safe.
The problem with the above if-construct is, that if 2 threads execute the above if-statement simultaneously, they may both call map.containsKey("key1") at the same time, they might both receive the answer that the map does not contain the key "key1" - meaning containsKey() returns false. In that case, both threads will continue into the if-statement body, and both insert a value into the ConcurrentMap. The second thread to insert its value will overwrite the value inserted by the first thread.
In the above example, having 2 threads both insert the static value "value1" may not be so big of a problem, but what if the value was computed - based on something the individual thread knows?
The solution to this problem is to use one of the atomic methods putIfAbsent() or computeIfAbsent() instead.
The putIfAbsent() method of the Java ConcurrentMap interface inserts the given key + value pair, if no key + value pair exists for the given key already. Here is an example of using the putIfAbsent() method:
ConcurrentMap map = new ConcurrentHashMap(); map.putIfAbsent("key1", "value1");
As you can see, there is now only a single method call to the ConcurrentMap - the putIfAbsent() method call. The ConcurrentMap implementation will make sure that only one thread at a time will be allowed to insert a value for the same key. The ConcurrentMap might allow multiple threads to insert key + value pairs for different keys - depending on its internal implementation (e.g. some implementations may only allow multiple concurrent insertions if the key + value pair lands in different "buckets" internally).
The computeIfAbsent() method is similar to the putIfAbsent() method - except it enables you to compute a value to insert for a given key, if no key + value pair is already stored for that key. Here is an example of using the computeIfAbsent() method:
ConcurrentMap map = new ConcurrentHashMap(); concurrentMap2.computeIfAbsent("key2", (key) -> { return "Value for key " + key.toString() + " : " + Thread.currentThread.getName(); });
This example computes and inserts a value for the given key, if the given key is already stored in the ConcurrentMap.
Only one thread at a time will be allowed to execute computeIfAbsent() for the same key. If two threads calls computeIfAbsent() for the same absent key, one of the threads will be allowed to compute and insert its value, and the other thread will not compute nor insert any value.
The value to be inserted is computed by the Java lambda expression that you pass in as the second parameter to the computeIfAbsent() method.
The Java ConcurrentMap interface also has a computeIfPresent() method, which will compute and insert a value into the ConcurrentMap if a given key is already present in the map. If not present, no value is computed nor inserted into the ConcurrentMap. Here is an example of using the computeIfPresent() method:
ConcurrentMap map = new ConcurrentHashMap(); concurrentMap2.computeIfPresent("key2", (key) -> { return "Value for key " + key.toString() + " : " + Thread.currentThread.getName(); });
In the example above, the ConcurrentMap does not contain any keys at all at the time the computeIfPresent() method is called, so no value is computed nor inserted into the ConcurrentMap.
Tweet | |
Jakob Jenkov |