Note that the phrase “Unless otherwise specified by the implementing class” has been removed from recent versions of the specification:
Performs the given action for each element of the Iterable
until all elements have been processed or the action throws an exception. Actions are performed in the order of iteration, if that order is specified. Exceptions thrown by the action are relayed to the caller.
Since a List
has a defined encounter order and performing an action in a defined order precludes concurrent execution, we can clearly say that concurrent execution is precluded explicitly in the specific case mentioned in your question.
The broader question whether Iterable
’s forEach
precludes concurrent execution of the specified action in general, when there’s no defined encounter order, can only be answered by resorting to the Principle of least astonishment, concluding that the specification should mention explicitly if concurrent execution was allowed under certain circumstances. As far as I can see, Java’s entire API specification adheres to this principle, so there’s no reason to assume that it doesn’t apply to this specific method.
Most notably, everyone is aware that the Stream
API allows parallel processing, because it has been prominently documented. Likewise, Arrays.sort(…)
does not permit a surprising parallel evaluation of the Comparator
, without mentioning it explicitely, but rather, an explicit use of Arrays.parallelSort(…)
is required to enable it.
The same applies even for the actual concurrent collections. E.g. when you call keySet().forEach(…)
on a ConcurrentHashMap
, it will not fail when there are concurrent updates, as the general weakly consistent iteration policy specifies, but also run sequentially on the caller’s thread, as every method does when not specified otherwise. You’d need a dedicated forEach
method for parallel processing.
So the use of AtomicInteger
is not justified. It’s a widespread bad programming style, pretending functional programming while still being stuck with thinking in loops, using forEach
and modifying a variable outside the function. Since this is not possible with local variables, AtomicInteger
is only used as container for an int
heap variable here. Using a single element int[]
array would do as well, but still isn’t recommended. Instead
stay with a plain loop, or
use a real functional approach, e.g.
int result = words.stream()
.mapToInt(word -> {
// count matches locally and return the int
})
.sum();
or
long result = words.stream()
.flatMap(word -> {
// return a stream of matches
})
.count();