繁体   English   中英

Java集合线程安全地将集合对象从一个线程传递到另一个线程

[英]Java thread-safe passing of collection objects from one thread to another

我有一个Java应用程序,它有工作线程来处理作业。 一个worker产生一个结果对象,比如说:

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在这里是一种“工作单元”。 passToGatherThread方法将设置的items传递给收集线程,其中只有一个在运行时存在。

这里不需要同步,因为竞争条件不会发生,因为只有一个线程(Gather-thread)读取items集。 AFAICS,Gather-thread可能看不到所有项目,因为该集合不是线程安全的,对吧?

假设我无法使passToGatherThread同步,因为它是第三方库。 我基本上担心的是收集线程由于缓存,VM优化等而没有看到所有项目。所以这里出现了一个问题:如何以线程安全的方式传递项目,以便Gather线程“看到”适当的项目?

这里似乎没有同步问题。 您为每个passToGatherThread创建一个新的Set对象,并在修改该集后执行此操作。 不会丢失任何对象。

如果不对集合进行任何修改,则许多线程可以同时访问Set(和大多数Java集合)。 这就是Collections.unmodifiableCollection的用途。

由于提到的passToGatherThread方法用作与其他线程的通信,因此它必须使用某种同步 - 并且每次同步都可确保线程之间的内存一致性。

另外 - 请注意,传递集合中对象的所有写入都是传递给另一个线程之前进行的。 即使将内存复制到线程的本地缓存中,它也具有与另一个线程中相同的未修改值。

您可以简单地使用Java为您的WorkerResult提供的Set一个线程安全实现。 参见例如:

另一种选择是使用Collections.synchronizedSet()

我已经考虑过(并讨论过)这个问题了很多,我想出了另一个答案,我希望这将是最好的解决方案。

传递同步集合在效率方面并不好,因为该集合上的每个后续操作都将同步 - 如果有许多操作,它可能被证明是一个障碍。

要点:让我们做一些假设(我不同意):

  • 提到的passToGatherThread方法确实不安全,但似乎不太可能
  • 编译器可以重新排序代码中的事件,以便在填充集合之前调用passToGatherThread

确保传递给gatherer方法的集合准备就绪并且完整的最简单,最简洁且可能最有效的方法是将集合推送到同步块中,如下所示:

synchronized(items) {
  passToGatherThread(items);
}

这样我们就可以在传递集合之前确保内存同步和有效的发生前序列,从而确保正确传递所有对象。

worker实现了callable并返回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;
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM