Будет ли код детерминированным, если задачи выполняются в случайном порядке
Что проверяют
Различаете ли вы порядок выполнения и результат: задачи могут идти в любом порядке, а итог при этом оставаться одинаковым. Следом обычно спрашивают, как собрать результаты в предсказуемом порядке и чем опасна общая изменяемая переменная.
Как отвечать
Короткий ответ: зависит от того, что задачи делают с общими данными и как собирается итог. Результат детерминирован, если выполнены три условия:
- нет гонок данных: задачи не пишут в общее состояние, либо доступ синхронизирован через
synchronized,AtomicInteger,LongAdder,ConcurrentHashMap.count++из нескольких потоков теряет обновления: это чтение, прибавление и запись, а не одна операция; - объединение не зависит от порядка: операция ассоциативна и коммутативна, как сложение целых или максимум. Сумма
intв любом порядке одна и та же даже с переполнением; - результаты собираются по месту, а не по времени:
ExecutorService.invokeAllвозвращает списокFutureв порядке отправки задач, как бы они ни выполнялись. Добавление в общий список по мере завершения даст каждый раз новый порядок.
Для стримов: parallelStream().forEach порядок не гарантирует, forEachOrdered и collect(Collectors.toList()) сохраняют порядок встречи элементов. reduce в параллельном стриме корректен только с ассоциативной операцией и настоящим нейтральным элементом.
Хорошая ловушка: числа с плавающей точкой. Сложение double не ассоциативно, поэтому параллельная сумма может отличаться от последовательной в последних знаках. В документации DoubleAdder сказано, что порядок накопления не гарантирован, значит итог может немного плавать от запуска к запуску. Для денег берут long в копейках или BigDecimal.
Проверять такое обычными юнит-тестами ненадёжно, для этого есть jcstress.
Где спотыкаются
Считают, что synchronized делает код детерминированным. Он убирает гонку, но порядок захвата замка остаётся случайным, и элементы в общем списке будут лежать по-разному.
Отвечают «независимые задачи всегда дают одно и то же» и забывают про время, Random, базу и сеть.
Путают параллельный forEach с упорядоченным и считают порядок строк в логах доказательством.