Ответ 1
Нет, вам это не нужно. В документации invokeAll указано, что все задания должны выполняться при их возврате. Таким образом, не должно быть никакого дополнительного доступа для сбора, когда вы достигнете оператора return.
Я улучшаю существующий алгоритм, который состоит из нескольких независимых шагов для использования параллельных задач. Каждая из задач создаст несколько объектов для хранения своих результатов. В конце концов, я хотел бы иметь список всех результатов для возврата из метода управления. На данный момент мой код выглядит примерно так.
private final ExecutorService pool = ...;
// A single task to be performed concurrently with other tasks.
private class WorkHorse implements Callable<Void> {
private final Collection<X> collect;
public WorkHorse(Collection<X> collect, ...) {
this.collect = collect;
}
public Void call() {
for (...) {
// do work
synchronized (this.collect) {
this.collect.add(result);
}
}
return null;
}
}
// Uses multiple concurrent tasks to compute its result list.
public Collection<X> getResults() {
// this list is supposed to hold the results
final Collection<X> collect = new LinkedList<X>();
final List<WorkHorse> tasks = Arrays.asList(
new WorkHorse(collect, ...), new WorkHorse(collect, ...), ...);
this.pool.invokeAll(tasks);
// ## A ##
synchronized (collect) {
return collect;
}
}
Я действительно нуждаюсь в synchronized
в "## A ##", чтобы принудительно установить связь с изменениями в рабочих задачах? Или я могу полагаться на все операции записи, которые произошли после того, как invokeAll
вернется и будет видимым для управляющего потока? И есть ли какая-то причина, почему я не должен возвращать коллекцию результатов из своего собственного блока synchronized
?
Нет, вам это не нужно. В документации invokeAll указано, что все задания должны выполняться при их возврате. Таким образом, не должно быть никакого дополнительного доступа для сбора, когда вы достигнете оператора return.
Вам не нужен второй synchronized
, если у вас есть первый. Как отмечает Зед, invokeAll()
будет блокироваться, пока все задачи не будут завершены. Между тем, синхронизация вокруг add()
гарантирует, что изменения в коллекции будут видны для всех потоков, включая исходный вызывающий поток.
Что касается того, нужен ли вам первый (который вы не спрашивали), я попытался удалить оба блока synchronized
и не смог на самом деле заставить его потерпеть неудачу, но наличие его там, вероятно, является более безопасной ставкой. Согласно javadoc для LinkedList
:
Если несколько потоков обращаются к LinkedList одновременно и, по крайней мере, один из потоков изменяет список структурно, это должно быть синхронизированный снаружи.
Другие реализации "2-го поколения" Collection
имеют похожие предупреждения.
Заметим, кстати, что нет ничего удивительного в синхронизации самой коллекции. Вы можете объявить отдельный мьютекс (любой старый Object
будет делать) во внешнем классе или синхронизировать с внешним экземпляром класса, и это будет работать так же хорошо, если все WorkHorse
синхронизируются на одном и том же.