2009-09-01 8 views
4

Я улучшаю существующий алгоритм, который состоит из нескольких независимых шагов для использования параллельных задач. Каждая из задач создаст несколько объектов для хранения своих результатов. В конце концов, я хотел бы иметь список всех результатов для возврата из метода управления. На данный момент мой код выглядит как-то такНужно ли мне синхронизировать результат вызова invokeAll?

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?

ответ

3

Нет, вам это не нужно. В документации invokeAll указано, что все задания должны выполняться при их возврате. Таким образом, не должно быть никакого дополнительного доступа для сбора, когда вы достигнете оператора return.

+0

Кроме того, если это были * не * семантика invokeAll(), синхронизированный блок не помог бы. Казалось бы, вам нужны какие-то ворота, которые предотвратили бы продолжение, пока все остальные потоки не будут написаны. – Avi

+0

Брайан Гетц говорит в «Java Concurrency in Action», что «все [поток] A, сделанное в или до синхронизированного блока, видимо [thread] B, когда он выполняет синхронизированный блок, защищенный одной и той же блокировкой. нет такой гарантии ». Утверждение в документации invokeAll, похоже, не отменяет это требование или делает это? – janko

+0

@janko Это утверждение верно, но не было гарантии, что ваш синхронизированный обратный блок не будет запущен до того, как все рабочие потоки достигнут синхронизированной части. invokeAll говорит, что потоки уже выполнили всю свою работу (т. Е. Достигли возврата null;), когда она вернется. – Zed

0

Вам не нужен второй synchronized, если у вас есть первый. Как отмечает Zed, invokeAll() будет блокироваться, пока все задачи не будут завершены. Между тем, синхронизация вокруг add() гарантирует, что изменения коллекции будут видны для всех потоков, включая исходный поток вызовов.

Что касается нужны ли вы первого один (который вы не просили) - я попытался удалить оба synchronized блоков и не мог фактически заставить его потерпеть неудачу, но имея ее там, вероятно, безопасной ставка , В соответствии с Javadoc для LinkedList:

Если несколько потоков доступа к LinkedList параллельно, и по крайней мере один из потоков изменяет список структурно, он должен быть синхронизированы извне.

Другие версии «2-го поколения» Collection имеют аналогичные предупреждения.

Обратите внимание, что нет ничего удивительного в синхронизации с самой коллекцией. Вы можете объявить отдельный мьютекс (любой старый Object) во внешнем классе или синхронизировать с внешним экземпляром класса, и это будет работать так же хорошо, если все WorkHorse s синхронизируются на одном и том же.