ConcurrentHashMap in Java is a thread safe Map implementation which provides another alternative to be used in a
multithreaded environment apart from HashTable or explicitly synchronizing HashMap.
ConcurrentHashMap is part of the java.util.concurrent
package.
- How is ConcurrentHashMap a better option
- Internal implementation of ConcurrentHashMap in Java
- Java ConcurrentHashMap constructors
- Java example creating a ConcurrentHashMap
- Null not allowed in Java ConcurrentHashMap
- ConcurrentHashMap in Java is thread safe
- Java ConcurretHashMap returns a fail-safe iterator
- Atomic operations in ConcurrentHashMap
- Advantages and disadvantages of ConcurrentHashMap
How is ConcurrentHashMap a better option
Other thread safe implementations like HashTable or explicit synchronization of HashMap synchronize all the methods on a single lock and all the methods are synchronized, even if methods are for retrieving elements. That makes these options very slow-
- Since whole collection is locked so only a single thread can access it at a given time.
- Since all the methods are synchronized so read operations are also slow.
ConcurrentHashMap in Java tries to address these issues-
- By not locking the collection for retrieval operations like get(). Concurrent read operations are permitted only the write operations are synchronized.
- Even for write operations whole collection is not locked but only the part of the table where the elements has to be put as per the calculated hashcode.
Internal implementation of ConcurrentHashMap in Java
For storing its element ConcurrentHashMap internally uses an array named table of type Node.
transient volatile Node<K,V>[] table;
Here Node class represents the key-value entry and defined as a static class with in the ConcurrentHashMap implementation. Node class has fields for storing key and value and also next field for holding the reference to the next node.
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; volatile V val; volatile Node<K,V> next; .....
Here note that in initial implementation of ConcurrentHashMap in Java 5 there was array Segment which was used and that provided concurrency level of 16 by default i.e. 16 threads could access 16 elements stored in different indexes of the array because each segment could be locked independently. But Java 8 onward internal implementation of ConcurrentHashMap is modified and now array named table is used and it mainly uses Compare-And-Swap (CAS) operations for concurrency during write operations.
Each array index in the table can still be independently locked by synchronizing the first node of that particular bucket.
Java ConcurrentHashMap constructors
- ConcurrentHashMap()- Creates a new, empty map with the default initial table size (16).
- ConcurrentHashMap(int initialCapacity)- Creates a new, empty map with an initial table size accommodating the specified number of elements without the need to dynamically resize.
- ConcurrentHashMap(int initialCapacity, float loadFactor)- Creates a new, empty map with an initial table size based on the given number of elements (initialCapacity) and initial table density (loadFactor).
- ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)- Creates a new, empty map with an initial table size based on the given number of elements (initialCapacity), table density (loadFactor), and number of concurrently updating threads (concurrencyLevel).
- ConcurrentHashMap(Map<? extends K,? extends V> m)- Creates a new map with the same mappings as the given map.
Java example creating a ConcurrentHashMap
In this example a ConcurrentHashMap is created and (key, value) pair added to it which are later displayed.
import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class CHMExample { public static void main(String[] args) { // Creating ConcurrentHashMap Map<String, String> carMap = new ConcurrentHashMap<String, String>(); // Storing elements carMap.put("1", "Audi"); carMap.put("2", "BMW"); carMap.put("3", "Jaguar"); carMap.put("4", "Mini Cooper"); for (Map.Entry<String, String> entry : carMap.entrySet()) { System.out.println("key- " + entry.getKey() + " value- " + entry.getValue()); } } }Output
key- 1 value- Audi key- 2 value- BMW key- 3 value- Jaguar key- 4 value- Mini Cooper
Null not allowed in Java ConcurrentHashMap
ConcurrentHashMap does not allow insertion of null as either key or value. So both of the following statements result in NullPointerException.
carMap.put(null, "Audi"); Exception in thread "main" java.lang.NullPointerException
carMap.put("1", null); Exception in thread "main" java.lang.NullPointerException
ConcurrentHashMap in Java is thread safe
ConcurrentHashMap in Java is safe to use in multi-threaded environment. Let’s see an example where we’ll first try to insert 400 elements in a HashMap (which is not thread safe) using 4 threads with each thread inserting 100 elements. Expected size of the HashMap is 400 after the execution.
import java.util.HashMap; import java.util.Map; public class MapSynchro implements Runnable{ private Map<String, String> testMap; public MapSynchro(Map<String, String> testMap){ this.testMap = testMap; } public static void main(String[] args) { Map<String, String> testMap = new HashMap<String, String>(); /// 4 threads Thread t1 = new Thread(new MapSynchro(testMap)); Thread t2 = new Thread(new MapSynchro(testMap)); Thread t3 = new Thread(new MapSynchro(testMap)); Thread t4 = new Thread(new MapSynchro(testMap)); t1.start(); t2.start(); t3.start(); t4.start(); try { t1.join(); t2.join(); t3.join(); t4.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Size of Map is " + testMap.size()); } @Override public void run() { System.out.println("in run method" + Thread.currentThread().getName()); String str = Thread.currentThread().getName(); for(int i = 0; i < 100; i++){ // adding thread name to make element unique testMap.put(str+i, str+i); try { // delay to verify thread interference Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }Output
in run methodThread-3 in run methodThread-0 in run methodThread-1 in run methodThread-2 Size of Map is 394
As you can see size is 394 in one run because of the thread interference.
Using ConcurrentHashMap eliminates such inconsistencies. You just need to change the following line in the code.
Map<String, String> testMap = new ConcurrentHashMap<String, String>();
Now the size is always 400.
Java ConcurretHashMap returns a fail-safe iterator
Iterator returned by ConcurrentHashMap is fail-safe and does not throw ConcurrentModificationException if the map is structurally modified at any time after the iterator is created.
public class FailSafeDemo { public static void main(String[] args) { Map<String, String> carMap = new ConcurrentHashMap<String, String>(); carMap.put("1", "Audi"); carMap.put("2", "BMW"); carMap.put("3", "Jaguar"); carMap.put("4", "Mini Cooper"); // iterating map Iterator<Map.Entry<String, String>> itr = carMap.entrySet().iterator(); while(itr.hasNext()) { Map.Entry<String, String> entry = itr.next(); System.out.println("Key is " + entry.getKey() + " Value is " + entry.getValue()); carMap.put("5", "Mercedes"); } System.out.println("Size- " + carMap.size()); } }Output
Key is 1 Value is Audi Key is 2 Value is BMW Key is 3 Value is Jaguar Key is 4 Value is Mini Cooper Key is 5 Value is Mercedes Size- 5
In the code, while iterating the ConcurrentHashMap a new element is added to it that does not result in ConcurrentModificationException being thrown.
Atomic operations in ConcurrentHashMap
Even though Java ConcurrentHashMap is thread safe but atomic operations may still give inconsistent result in multi-threaded environment. For example a scenario as follows.
Integer oldVal = CHMMap.get(key); Integer newVal = (oldVal== null) ? 1 : oldVal + 1; // newValue stored by another thread CHMMap.put(key, newValue);
Here if an executing thread is preemepted by another thread after the execution of this line-
Integer newVal = (oldVal== null) ? 1 : oldVal + 1;
then the value put back in the ConcurrentHashMap
may not be correct. In such scenarios it is better to use atomic operations. Some of the atomic operations in the
ConcurrentHashMap class are-
- putIfAbsent(K key, V value)- If the specified key is not already associated with a value, associates it with the given value.
- remove(Object key, Object value)- Removes the entry for a key only if currently mapped to a given value.
- computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)- If the specified key is not already associated with a value, attempts to compute its value using the given mapping function and enters it into this map unless null.
- computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)- If the value for the specified key is present, attempts to compute a new mapping given the key and its current mapped value.
- compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)- Attempts to compute a mapping for the specified key and its current mapped value (or null if there is no current mapping).
- merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)- If the specified key is not already associated with a (non-null) value, associates it with the given value.
compute()
, scenario as listed above can be written as follows-
CHMMap.compute(key, (k,v)-> v == null ? 1 : v + 1);
Advantages and disadvantages of ConcurrentHashMap
- ConcurrentHashMap in Java performs better if there are more reads than writes in a multi-threaded environment as the concurrent read operations are permitted. Since retrieval operations are non-blocking, so may overlap with update operations (including put and remove). Thus the concurrent retrievals may or may not reflect insertion or removal of some entries.
- If there are more writes and updates in the ConcurrentHashMap and HashCode implementation is not proper then many elements may have the same hashcode. In that scenario most of the threads will need to access the same table index where the elements with the same hashcode are to be stored resulting in degraded performance.
- Iterators in ConcurrentHashMap are designed to be used by only one thread at a time.
That's all for the topic Java ConcurrentHashMap With Examples. If something is missing or you have something to share about the topic please write a comment.
You may also like
- Java CopyOnWriteArrayList With Examples
- Java ConcurrentSkipListMap With Examples
- HashMap Vs ConcurrentHashMap in Java
- Java HashMap compute() With Examples
- Java String Class With Method Examples
- Java Program to Find Longest Palindrome in The Given String
- Spring @Conditional Annotation
- What is Data Locality in Hadoop
- Spring Boot With JSP Example
- React useState Hook With Examples
No comments:
Post a Comment