Одна жизнеспособная стратегия заключается в том, чтобы каждая задача ссылалась на общую поточно-безопасную коллекцию. Это работает, но имеет некоторые очевидные ограничения в отношении того, как вы используете коллекцию (наиболее важно, изменяете ли вы ее) и ожидаемый параллелизм. Если вам нужно изменить коллекцию из задачи, вы будете подвергаться некоторому уровню конкуренции за запись в общую коллекцию. В зависимости от количества потоков, какой сбор (потокобезопасный или параллельный) и т. Д. Это может быть узким местом. Даже чтение может вызвать конфликт в определенных типах коллекций.
Другой вариант - дать каждой задаче необходимые входные данные, дать ей возможность вычислить результат и затем обработать результаты. Это может быть сделано параллельно или нет, в зависимости от ваших потребностей.
Вы обязательно должны использовать ExecutorService в любом случае, поскольку он позволяет объединять пул потоков и очередь запросов, где потоки будут извлекать и выполнять задачи по мере необходимости. CompletionService также может добавить очередь результатов, если это полезно.