lundi 8 février 2021

How to test thread safety for Java stream forEach

I was looking into Venkat's talk on Exploring collectors. In it, he mentioned that forEach mutates list and does not enforce immutability which is bad for multi-threaded code. I want to try to test it with unit/integration tests. Is there a way to test the following code, (does not matter if the multi-threaded tests are flaky because they are bound to happen):

import java.util.*;
import java.util.stream.Collectors;


// Source: https://www.youtube.com/watch?v=pGroX3gmeP8&t=1980s
public class Sample {

    public static List<Person> createPeople() {
        return Arrays.asList(new Person("Ayman", 20), new Person("Brad", 50));
    }

    public static void main(String[] args) {
        // Using `forEach`. Bad for multi-threaded code 
        nameUpperCaseGreaterThan30UsingForEach();
    }

    // Don't do this
    private static void nameUpperCaseGreaterThan30UsingForEach() {
        List<String> namesOlderThan30 = new ArrayList<>();
        createPeople().stream() // Parallel Stream - Multi-thread NOT safe
                .filter(person -> person.getAge() > 10) // Pure Function
                .map(Person::getName) // Pure Function
                .map(String::toUpperCase) // Pure Function
                .forEach(name -> namesOlderThan30.add(name)); // Impure function: mutates list & shared mutability. Parallel execution will result RACE condition
        ;

    }

    // Do this
    private static void nameUpperCaseGreaterThan30TheRightWay() {
        List<String> namesOlderThan30Correct =
        createPeople().stream() // Parallel Stream - Multi-thread safe
                .filter(person -> person.getAge() > 10) // Pure Function
                .map(Person::getName) // Pure Function
                .map(String::toUpperCase) // Pure Function
//                .reduce(
//                        new ArrayList<>(),
//                        (names, name) -> {
//                            names.add(name);
//                            return names;
//                        },
//                        (names1, names2) -> {
//                            names1.addAll(names2);
//                            return names1;
//                        }
//                );
                .collect(Collectors.toList()); 

    }
}

// Person.class
@Getter
@Setter
@NoArgsConstructor
public class Person {
    private String name;
    private int age;
}

I do not know any way to use JUnit in a multi-threaded codebase? Any Java-based framework could be helpful to test this hypothesis.

Aucun commentaire:

Enregistrer un commentaire