Parallelism in ConcurrentHashMap

ConcurrentHashMap is used extensively in multi-threaded applications. The examples of multi-threaded applications are online gaming applications, chat applications, it adds the benefit of concurrency to the application. To make application more  concurrent in nature, ConcurrentHashMap introduces a concept called ‘Parallelism’.

In this article we will learn more about parallelism in ConcurrentHashMap.

What is Parallelism?

Basically, parallel computing divides a problem into the subproblems and solves those subproblems parallely and finally joins the results of the subproblems. Here the subproblems will run in separate threads. 

Java Support to Parallelism in ConcurrentHashMap

In order to make use of parallelism in ConcurrentHashMap, we need to use Java 1.8 version onwards. Parallelism is not supported in Java versions less than 1.8.

Common framework for parallel processing

Java has introduced a framework called ‘fork and join’ that will enable parallel computing. It makes use of java.util.concurrent.ForkJoinPool  API to achieve parallel computing. This API is used to implement parallelism in ConcurrentHashMap. 

Parallel Methods in ConcurrentHashMap

ConcurrentHashMap effectively uses parallel computing with the help of parallelism threshold. It is a numerical value and the default value is 2. 

These are the following methods that are having parallelism capabilities in ConcurrentHashMap. 

  • forEach()
  • reduce()
  • reduceEntries()
  • forEachEntry()
  • forEachKey()
  • forEachValue()

The ConcurrentHashMap deals with the parallelism in a slightly different way and you will understand that if you take a look at the method arguments of these above methods.Each of these methods can take parallelism threshold as an argument.

First of all, parallelism is an optional feature. We can enable this feature by adding the proper parallel threshold value in the code.

Usage of ConcurrentHashMap without parallelism

Let us take an example of replacing all the string values of a ConcurrentHashMap. This is done without using parallelism.

Example:
ConcurrentHashMap.forEach((k,v) -> v=””); 

It is pretty straight forward and we are iterating all the entries in a ConcurrentHashMap and replace the value with an empty string. In this case, we are not using parallelism

Usage of ConcurrentHashMap with parallelism

Example:
ConcurrentHashMap.forEach(2, (k,v) -> v=””);

The above example  iterates a ConcurrentHashMap and replaces the value of a map with an empty string.The arguments to the forEach() method are parallelism threshold  and a functional inerface.In this case, the problem will be divided into subproblems. 

Here the problem is to replace the value of  the ConcurrentHashMap with an empty string.This is achieved by dividing this problem into subproblems , i.e. creating separate threads for subproblems and each thread will focus on replacing the value with an empty string.

What happens when parallelism is enabled?

When the parallelism threshold is enabled, JVM will create threads and each thread will run to solve the problem and join the results of all the threads.The significance of this value is that if the number of records has reached a certain level (threshold), then only JVM will enable parallel processing. In the above example. If there is more than 1 record in the map, then, the application will enable parallel processing.

This is a cool feature and we can control the parallelism by adjusting the parallelism threshold value. This way we can take advantage of parallel processing in the application. 

Take a look at the another example below:

ConcurrentHashMap.forEach(10000, (k,v) -> v=””);

In this case, the parallelism threshold is 10000 which means that if the number of records is less than 10000, then JVM will not enable parallelism at the time of replacing the values with an empty string.

Fig: Full code example without parallelism
Fig: Full code example with parallelism

In the above example, parallelism threshold is 10000.

Performance Comparison of Parallel processing

The following code simply replaces all the values in the map with empty string. This ConcurrentHashMap contains more than 100000 entries in it. Let’s compare the performance of the below code without and with parallelism.

Fig: Comparison of the code both with and without parallelism

After running the above code, you can see there is a little performance improvement in case of normal forEach operation.

time without parallelism->20 milliseconds 
time with parallelism->30 milliseconds

This is because the number of records in the map is fairly low. 

But, if we add 10 million records in the map, then the parallelism really wins!. It takes less time to process the data. Take a look at the code in the below image:

Fig: Threshold of the code with and without parallelism

The above code replaces all the values in the ConcurrentHashMap with an empty string without using parallelism. Next, it uses parallelism to replaces all the values of the ConcurrentHashMap with string one. This is the output:

time without parallelism->537 milliseconds
time with parallelism->231 milliseconds

You can see that in case of parallelism, it only takes half of the time.

Note: The above values are not constant. It may produce different results in different systems.

Thread dump analysis for Parallelism

When we enable parallelism in the code, JVM uses ForkJoinPool framework to enable the parallel processing. This framework creates few worker threads based on the demand in the current processing. Let’s take a look at the thread dump analysis with parallelism enabled using the fastthread.io tool for the above code.

Fig: fastThread report showing the thread count with parallelism enabled
Fig: fastThread report showing the identical stacktrace by enabling parallelism

There are a total 42 threads used in the above code in this case. There are 2 waiting, 39 are running and 1 is in timed waiting.We can analyze more behavior using the tool. For a detailed analysis, you can check with fastthread.io tool. 

You can understand from the above picture that it is using more threads.

The reason for too many running threads is that it is using ForkJoinPool API. This is the API that is responsible for implementing the `parallelism` behind the scene. You will understand this difference when you look at the next section.

Link to the report is given below:

https://fastthread.io/my-thread-report.jsp?p=c2hhcmVkLzIwMjMvMDUvMjUvdGRhd2l0aHBhcmFsbGVsaXNtLnR4dC0tNC0wLTI3&

Thread dumps Analysis without Parallelism

Let us understand the thread dump analysis without enabling parallelism.

Fig: fastThread report showing thread count without parallelism enabled
Fig: fastThread report showing the identical stacktrace without enabling parallelism

If you take a close look at the above image, then you can understand that there are only a few threads used. In this case, there are only 35 threads as compared to the previous image. There are 32 runnable threads in this case. But, waiting and timed_waiting threads are 2 and 1 respectively. The reason for the reduced number of runnable threads in this case is that it is not calling the ForkJoinPool API.

The link to the report is given below:

https://fastthread.io/my-thread-report.jsp?p=c2hhcmVkLzIwMjMvMDUvMjUvdGRhd2l0aG91dHBhcmFsbGVsaXNtLnR4dC0tNC0xOS01MA==&

This way fastthread.io tool can provide a good insight into the thread dump internals in a very smart way.

Summary

We focused on parallelism in the ConcurrentHashMap and how this feature can be used in the application. Also, we understood what happens with the JVM when we enable this feature. Parallelism is a cool feature and can be used quite well in the modern concurrent applications.

Leave a Reply

Powered by WordPress.com.

Up ↑

%d bloggers like this: