Reputation: 337
Methods getFirst()
and getSecond()
of this class is invoked concurrently. It is a part of a web app.
Internal maps populated as well with no concurrency.
public class MyClass {
private Map<String, List<List<String>>> first;
private Map<String, List<List<String>>> second;
public MyClass() {
first = new ConcurrentHashMap<>();
second = new ConcurrentHashMap<>();
}
public Set<String> getFirst(String key, String token, int a, int b) {
return get(first, key, token, a, b);
}
public Set<String> getSecond(String key, String token, int a, int b) {
return get(second, key, token, a, b);
}
private Set<String> get(final Map<String, List<List<String>>> map, final String key, final String token,
final int a, final int b) {
Set<String> result = new TreeSet<>();
map.get(key).stream().filter(i -> i.size() <= b && i.size() >= a).forEach(
s -> result
.addAll(s.stream().filter(p -> StringUtils.containsIgnoreCase(p, token)).collect(Collectors.toList())));
return result;
}
}
I tested it with something like ab -n 10000 -c 100
(Apache's utility). And I log it. I get the same set all times. But if I change map.get(key).stream()
to map.get(key).parallelStream()
and do the same steps I get sometimes different result size (always smaller).
What is it?
Upvotes: 3
Views: 1498
Reputation: 100209
You are using TreeSet.addAll()
inside the forEach
of parallel stream. The forEach
body can be executed simultaneously several times in different threads for different elements and TreeSet
is not thread-safe. To fix the problem quickly you can either synchronize modification of result
or use forEachOrdered
. However it would be cleaner and more performant to flatMap
your stream and collect it at once without forEach
. Try this version:
return map.get(key).stream()
.filter(i -> i.size() <= b && i.size() >= a)
.flatMap(List::stream).filter(p -> StringUtils.containsIgnoreCase(p, token))
.collect(Collectors.toCollection(TreeSet::new));
Upvotes: 5