{{announcement.body}}
{{announcement.title}}

Lambdas for Concurrent Maps

DZone 's Guide to

Lambdas for Concurrent Maps

Here come lambda expressions into the play. Lambda expressions help us to avoid these race conditions elegantly. Let us see how.

· Java Zone ·
Free Resource

The package java.util.concurrent contains two concurrent maps, the class ConcurrentHashMap, and the class ConcurrentSkipListMap. Both classes are thread-safe and high performant. But using them is error-prone because of reading modify write race conditions. Here come lambda expressions into the play. Lambda expressions help us to avoid these race conditions elegantly.

Let us see how.

Read Modify Write Race Condition

The race condition happens when we read an element from the map, modify this element, and write the element back into the map. Like in the following example:

Java
 




x
33


1
import com.vmlens.api.AllInterleavings;
2
public class TestUpdateWrong {
3
    public void update(ConcurrentHashMap<Integer, Integer> map) {
4
        Integer result = map.get(1);
5
        if (result == null) {
6
            map.put(1, 1);
7
        } else {
8
            map.put(1, result + 1);
9
        }
10
    }
11
    @Test
12
    public void testUpdate() throws InterruptedException {
13
        try (AllInterleavings allInterleavings = 
14
            new AllInterleavings("TestUpdateWrong");) {
15
            while (allInterleavings.hasNext()) {
16
                final ConcurrentHashMap<Integer, Integer> map = 
17
                        new ConcurrentHashMap<Integer, Integer>();
18
                Thread first = new Thread(() -> {
19
                    update(map);
20
                });
21
                Thread second = new Thread(() -> {
22
                    update(map);
23
                });
24
                first.start();
25
                second.start();
26
                first.join();
27
                second.join();
28
                assertEquals(2, map.get(1).intValue());
29
            }
30
        }
31
    }
32
 
33
}



You can download the source code of the example from Github here.

Here we implement a per-key counter. In the update method, we initialize the count to 1 if no mapping exists otherwise we increment the count by one. To reproduce the race condition we update the map from two different threads. After both threads are stopped we check if the value is indeed two.

To test all thread interleavings we put the complete test in a while loop iterating over all thread interleavings using the class AllInterleavings from vmlens, line 15. Running the test we see the following error:

Java
 




xxxxxxxxxx
1


1
java.lang.AssertionError: expected:<2> but was:<1>



To see why the result is one and not two as expected we can look at the report vmlens generated:

operation method

The problem is that the update method is not atomic. Both threads can read the same. This lets the last thread override the result of the first thread.

Avoiding Read Modify Write Race Condition With Lambda Expressions

To avoid this race condition we need a way to execute all three operations, the read, the modification, and the write-inn one atomic method call. The method compute does exactly this using a lambda expression:

Java
 




xxxxxxxxxx
1


1
public void update(
2
    ConcurrentHashMap<Integer,Integer>  map ) {
3
    map.compute(1, (key, value) -> {
4
        if (value == null) {
5
            return 1;
6
         } 
7
            return value + 1;
8
    });
9
}



Now the read modifies write operations happen in one atomic method and the race disappears.

Lambdas Should Be Pure

What properties should a lambda function have?

The lambda expressions in the ConcurrentHashMap get executed under a synchronized block on a bin entry of the hash map. Therefore you must not call another writing operation of this ConcurrentHashMap instance. Otherwise, this can lead to deadlocks, as shown in this blog post.

When we use a ConcurrentSkipListMap on the other side our lambda expressions might be called multiple times, since this map uses an optimistic concurrency scheme. To not depend on implementation details the general advice is to make lambda expression pure. This means they should have no side effects and simply calculate a new immutable value from a given immutable value.

Conclusion

Lambda expressions help to avoid read modify write race conditions elegantly. When using lambda expressions the elements in the collection should be immutable. And the lambda function itself should be pure.

Topics:
concurrent ,java ,lambda expression

Published at DZone with permission of Thomas Krieger , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}