我有一个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线程“看到”适当的项目?
答案 0 :(得分:1)
这里似乎没有同步问题。您为每个passToGatherThread创建一个新的Set对象,并在修改该集后执行此操作。没有物体会丢失。
Set(和大多数Java集合)可以由许多线程同时访问,前提是不对集合进行任何修改。这就是Collections.unmodifiableCollection
的用途。
由于提到的passToGatherThread
方法用作与其他线程的通信,因此它必须使用某种同步 - 并且每个同步都可确保线程之间的内存一致性。
另外 - 请注意,传递集合中对象的所有写入都是在传递给另一个线程之前。即使将内存复制到线程的本地缓存中,它也具有与其他线程相同的未修改值。
答案 1 :(得分:1)
您可以简单地使用Java为Set
提供的WorkerResult
的一个线程安全实现。例如见:
另一种选择是使用Collections.synchronizedSet()
。
答案 2 :(得分:1)
我已经考虑(并讨论过)这个问题很多,我想出了另一个答案,我希望这将是最好的解决方案。
传递同步集合在效率方面并不好,因为该集合上的每个后续操作都将同步 - 如果有许多操作,则可能会被证明是一个障碍。
要点:让我们做一些假设(我不同意):
passToGatherThread
方法确实不安全,但似乎不太可能passToGatherThread
确保传递给gatherer方法的集合准备就绪并且完整的最简单,最简洁且可能最有效的方法是将集合推送到同步块中,如下所示:
synchronized(items) {
passToGatherThread(items);
}
这样我们就可以在传递集合之前确保内存同步和有效的发生前序列,从而确保所有对象都能正确传递。
答案 3 :(得分:0)
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;
}
}