Java 8 ConcurrentHashMap compute() and computeIfPresent() Example

The JDK 8 has added several useful methods in existing interfaces e.g. java.util.Map, java.util.Collection, and java.util.concurrent.ConcurrentMap. Thanks to default methods, the much-needed evolution of existing interfaces become possible. Out of many useful methods, one method which stands out to me is the compute() method, which allows you to update a value in ConcurrentHashMap atomically. As per Java documentation, The compute() function tries to compute a mapping for the specified key and its current mapped value (or null if there is no current mapping). The entire function is performed atomically.

Some attempted update operations on this map by other threads may be blocked while the computation is in progress, so the computation should be short and simple, and must not attempt to update any other mappings of this Map. In simple words, you can use this method to atomically update a value for a given key in the ConcurrentHashMap.

You can use compute() method to update an existing value inside ConcurrenHashMap. For example, to either create or append a String msg to a value mapping:

map.compute(key, (k, v) -> (v == null) ? msg : v.concat(msg))

If the function returns null, the mapping is removed (or remains absent if initially absent).

If the function itself throws an (unchecked) exception, the exception is rethrown, and the current mapping is left unchanged

There are also variants of compute() method e.g. computeIfPresent() and computeIfAbsent(), which computes the new value only if an existing value is present or absent.

For example, you can update a map of LongAdder using computeIfAbsent as shown below:

map.computeIfAbsent(key, k -> new LongAdder()).increment();

Here the constructor of LongAdder class will only be called when a new counter is actually needed. If a value exists then it is returned from ConcurrentHashMap, similar to the putIfAbsent() method.  You can further see What's New in Java 8 to learn more about new methods added to the existing API in Java 8.

How to use compute() method in Java 8

Here is a Java program to demonstrate how to use the compute() method to atomically update a value for a given key in a ConcurrentHashMap.

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.LongAdder;

 * Java Program to use compute() method of Java 8
 * to atomically update values in ConcurrentHashMap
public class Hello {

  public static void main(String[] args) throws Exception {

    // a ConcurrentHashMAp of string keys and Long values
    ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
    map.put("apple", 3);
    map.put("mango", 4);
    System.out.println("map before calling compute: " + map);

    // in JDK 8 - you can also use compute() and lambda expression to
    // atomically update a value or mapping in ConcurrentHashMap
    map.compute("apple", (key, value) -> value == null ? 1 : value + 1);
    System.out.println("map after calling compute on apple: " + map);

    // you can also use computeIfAbsent() or computeIfPresent() method
    // Constructor of LongAdder will be only called when a value for
    // given key is not exists
    ConcurrentMap<String, LongAdder> map2 = new ConcurrentHashMap<>();
    System.out.println("map with LongAdder before calling compute: " + map2);
    map2.computeIfAbsent("apple", key -> new LongAdder()).increment();
    map2.computeIfAbsent("mango", key -> new LongAdder()).increment();
    map2.computeIfAbsent("apple", key -> new LongAdder()).increment();
    System.out.println("map with LongAdder after calling compute on 
                            apple, mango, apple: " + map2);


map before calling compute: {apple=3, mango=4}
map after calling compute on apple: {apple=4, mango=4}
map with LongAdder before calling compute: {}
map with LongAdder after calling compute on apple, mango, apple: {apple=2, mango=1}

Now, let's try to understand what's happening here.

In the first line, we have simply created a Concurrent HashMap with two keys apple and mango, and their values are their count e.g. how many apples we have and how many mangoes we have.

Now, we got one more apple and we need to update the count on our bucket, for that, we are calling the compute() method as shown below:

map.compute("apple", (key, value) -> value == null ? 1 : value + 1);

This is just incrementing the value for this key, hence the count of "apple" moved from 3 to 4, which is shown in our second print statement.

In the second example, we have an empty ConcurrentHashMap and our job is to add keys and their counts in real time. This is suited for scenarios like you are doing a sale and you need to keep track of how many copies of a particular book or course is sold.

Another useful scenario is reading through a text file and printing the count of each word appear in the file.

Anyway, the key point here is that the map is initially empty which is shown in the third print statement. After that, we have added 2 apples and 1 mango in this map using computeIfAbsent() method and it has created a LongAdder object for each key and stored that as value.

The good thing about this method is that LongAdder object is only created when a key is first time added i.e. it was absent initially, after that, the count is incremented in LongAdder object.

This is a much better way to increase a counter in a concurrent Java application, but LongAdder is only available from Java 8.

That's why when we printed map2, we see that there are two apples and 1 mango is present in our concurrent map. You can further see The Ultimate Java 8 Tutorial - From beginner to professional to learn more about LongAdder and several other useful new classes added in JDK 8 API.

Here is the screenshot of this program with output:

Java 8 compute() and computeIfPresent() Example

Important points about compute() method of Java 8

Now that you know what is compute() method and what does it do and how to use it in a Java program, its time to revise some of the important things about this method.

Here are a couple of points which is worth remembering:

1) It allows you to atomically update a value in ConcurrentHashMap.

2) It has variants e.g. computeIfAbsent() and computeIfPresent() which computes values accordingly.

3) You can also use merge() in place of the compute() for updating an existing value.

That's all about how to use the compute() method in Java 8. You can use this method to update the values of a particular key in ConcurrentHashMap. You can also use to insert count for new keys. It's particularly useful for populating a concurrent hash map with keys and their counts e.g. reading a text file and print counts of all words.

Further Reading
What's New in Java 8
Java SE 8 for the Impatient
The Complete Java MasterClass

Other Java 8 tutorials you may like:

Thanks for reading this article so far. If you like this tutorial then please share with your friends and colleagues, if you have any question or doubt then please drop a comment.

P. S. - If you are looking for some free courses to learn recent changes on Java 8 and Java 9 then you can also see this list of Free Java 8 and 9 Courses for Programmers.

No comments:

Post a Comment