Java 8: New features in ConcurrentHashMap

The java.util.concurrent got a massive overhaul with the release of Java 8.

We’ve been introduced to whole new classes like CompletableFuture that brought a long awaited improvement to asynchronous work.

We’ve also seen a lot of new methods being introduced in already existing classes and interfaces - ConcurrentHashMap being one of them. This is to utilize lambdas and support the increased focus on concurrency.

To get familiar with these features — consider the following scenario.

We have a map of tags and their associated articles — resulting in a ConcurrentHashMap<String, List<Article>>.

Executing a function for each key-value pair

One of the methods that where introduced to a lot of big interfaces — Map no exception — was the forEach method.

It basically iterates your data — in our case the key-value pairs — executing a function of type Consumer.

A Consumer doesn’t return anything, so any changes you want to happen have to be side effects of your Consumer.

Let's give it a go by printing each tag and the number of associated articles.

map.forEach((k, v) -> System.out.println(k + "contains " + v.size() + " articles"));  

Adding parallelism threshold

ConcurrentHashMap is all about concurrency and parallelism. So it shouldn’t be of any surprise that you’ll find ways of controlling how this parallelism should work.

Through an extra parameter — parallelism threshold — we can say how many elements are needed for an operation to be executed in parallel.

The parallelism threshold is available in most of the new methods — including forEach.

Let’s see how this variant of forEach looks as well.

map.forEach(4, this::someHeavyOperation);  

In this example, the method will run in parallel when the size of the map reaches the parallelism threshold being 4.

Adding a transformer

There are also a versions of the forEach where we can add a transformer.

 map.forEach(1, (k, v) -> "There is " + v.size() + " articles about " + k, System.out::println);

The transformer transforms the data before sending it to the Consumer — pretty much like executing a map function on the key-value pair before passing it along.

A powerful search through a map

Let's continue by looking at another method — search.

The idea behind the search method is that you provide a search function to find a key-value pair.

The function you provide not only define what's a qualified key-value pair, but also the result that will be returned.

If the current key-value pair doesn't qualify, the provided function needs to return null to make the search continue.

So what if no pair qualify? Well, then the search will simply return null.

Let's see how this actually works by searching for a tag that contains more than 10 articles.

map.search(1, (k, v) -> {  
  return v.size() > 10 ? return k : null;
});

The first parameter is the parallelism threshold that we’ve discussed earlier, while the next argument is the function describing our search.

The search function first check if the size of the list of articles is more then 10, then returns the tag if it qualifies.

Searching based on the keys

As with forEach, there are many variants of search.

searchKeys is one variant — taking a function with only the key as a parameter.

map.searchKeys(1, key -> key.equals("Java") ? key : null);  

Searching based on the values

We also got searchValues — taking a function only with the value as a parameter.

To see how this works, let's create a search function that finds the first list of articles that is not empty, before returing the first element in this list.

map.searchValues(1, v -> !v.isEmpty() ? v.get(0) : null);  

Accumulating the data in a map

reduce is a great operator commonly used in most functional languages.

With the release of Java 8 — this also got widely available in different interfaces and classes in Java — including ConcurrentHashMap.

It’s actually added 19 different variants of the reducer in ConcurrentHashMap.

Let’s look at a few.

A standard reduce

Assuming that an article only appears in one of the lists — let’s use reduce to count how many articles our map contains.

map.reduce(100, (k, v) -> v.size(), (total, elem) -> total + elem);  

Again, we see the parallelism threshold as a first parameter.

The next parameter is a transformer — also seen in earlier examples. In our example we use the transformer to map from a key-value pair, to the count of articles for that given tag.

The last function is the reducer — combining all the resulting elements from the transformation using a BiFunction.

In our case, it’ll add all the counts of articles together — resulting in the total number of articles in our map.

Reduce the keys

If you don't care about the values of the map, you could use reduceKeys to only define a reducer for your keys.

There are two versions of reduceKeys — one with a transformer and one version without.

Let's look at an example without a transformer, where we find the first tag based on the alphabetic order.

map.reduceKeys(1, (k1, k2) -> k1.compareTo(k2) < 0 ? k1 : k2);  

Reduce the values

If keys are not what you're interested in — only the values —reduceValue could be a nice fit.

As with reduceKeysreduceValues comes in a version with a transformer and a version without.

This time let's create an example using a transformer, where we rewrite our first example — counting the number of articles in our map of tags.

 map.reduceValues(1, List::size, (total, elem) -> total + elem);

Further reading

Make sure to checkout the documentation for the complete list of available methods in ConcurrentHashMap.