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;
}
}