Java thread-безопасная передача объектов коллекции из одного потока в другой

у меня есть приложение Java, которое имеет рабочие потоки для обработки заданий. Рабочий производит объект результата, говорит что-то вроде:

class WorkerResult{
    private final Set<ResultItems> items;
    public Worker(Set<ResultItems> pItems){
         items = pItems;
    }
}

когда работник заканчивает, он делает эту операцию:

 ...
 final Set<ResultItems> items = new SomeNonThreadSafeSetImplSet<ResultItems>();
 for(Item producedItem : ...){
      items.add(item);
 }
 passToGatherThread(items);

на items set-это своего рода" единица работы " здесь. The passToGatherThread метод передает items набор для сбора ниток, из которых только один существует во время выполнения.

синхронизация здесь не требуется, так как условия гонки не могут произойти, потому что только один поток (Gather-thread) читает items set. AFAICS, Gather-thread может не видеть все элементы, потому что набор не является потокобезопасным, верно?

предположим, я не могу сделать passToGatherThread синхронизировано, скажем, потому, что это сторонняя библиотека. Я в основном боюсь, что поток gather не видит всех элементов из-за кэширования, оптимизации VM и т. д. Итак, возникает вопрос: Как передать набор элементов потокобезопасным образом, чтобы поток Gather "видел" правильный набор предметы?

4 ответов


Я много думал (и обсуждал) этот вопрос, и я придумал другой ответ, который, я надеюсь, будет лучшим решением.

передача синхронизированной коллекции не является хорошей с точки зрения эффективности, потому что каждая последующая операция в этой коллекции будет синхронизирована - если есть много операций, это может оказаться препятствием.

к делу: давайте сделаем некоторые предположения (с которыми я не согласен):

  • в упомянуто passToGatherThread метод действительно небезопасен, каким бы невероятным он ни казался
  • компилятор может изменить порядок событий в коде так, чтобы passToGatherThread вызывается перед заполнением коллекции

самый простой, самый чистый и, возможно, самый эффективный способ гарантировать, что коллекция, переданная методу сборщика, готова и завершена, - это поместить толчок коллекции в синхронизированный блок, например:

synchronized(items) {
  passToGatherThread(items);
}

таким образом, мы гарантируем синхронизацию памяти и допустимая последовательность happen-before перед передачей коллекции, таким образом, убедитесь, что все объекты переданы правильно.


похоже, здесь нет проблемы синхронизации. Вы создаете новый объект Set для каждого passToGatherThread и делаете это после изменения набора. Никакие предметы не будут потеряны.

Set (и большинство коллекций Java) могут быть доступны одновременно многими потоками при условии, что не производится никаких изменений в коллекции. Вот что!--0--> для.

, поскольку указанные passToGatherThread метод служит связью с другим потоком, он должен использовать какую-то синхронизацию -- и каждая синхронизация обеспечивает согласованность памяти между потоками.

Также-обратите внимание, что все записи на объекты в переданной коллекции сделаны до он передается в другой поток. Даже если память копируется в локальный кэш потока, она имеет то же немодифицированное значение, что и в другом потоке.


вы можете просто использовать одну из потокобезопасных реализаций Set что Java обеспечивает для вашего WorkerResult. См., например:

другой вариант-использовать Collections.synchronizedSet().


рабочий реализует вызываемый и возвращает WorkerResult:

class Worker implements Callable<WorkerResult> {
    private WorkerInput in;

    public Worker(WorkerInput in) {
        this.in = in;
    }

    public WorkerResult call() {
        // do work here
    }
}

затем мы используем ExecutorService для управления пулом потоков и собираем результаты с помощью Future.

public class PooledWorkerController {

    private static final int MAX_THREAD_POOL = 3;
    private final ExecutorService pool = 
       Executors.newFixedThreadPool(MAX_THREAD_POOL);

    public Set<ResultItems> process(List<WorkerInput> inputs) 
           throws InterruptedException, ExecutionException{         
        List<Future<WorkerResult>> submitted = new ArrayList<>();
        for (WorkerInput in : inputs) {
            Future<WorkerResult> future = pool.submit(new Worker(in));
            submitted.add(future);
        }
        Set<ResultItems> results = new HashSet<>();
        for (Future<WorkerResult> future : submitted) {
            results.addAll(future.get().getItems());
        }
        return results;
    }
}