我们的应用程序要求所有工作线程在定义的点进行同步。为此我们使用CyclicBarrier
,但它似乎没有很好的扩展。有超过八个线程,同步开销似乎超过了多线程的好处。 (但是,我无法用测量数据支持这一点。)
编辑:非常频繁地进行同步,大约为100k到1M次。
如果许多线程的同步“很难”,它是否有助于构建同步树?线程1等待2和3,它们分别等待4 + 5和6 + 7等;完成后,线程2和3等待线程1,线程4和5等待线程2等。
1
| \
2 3
|\ |\
4 5 6 7
这样的设置会降低同步开销吗?我很感激任何建议。
另请参阅此特色问题:What is the fastest cyclic synchronization in Java (ExecutorService vs. CyclicBarrier vs. X)?
答案 0 :(得分:2)
有超过8个线程,同步开销似乎超过了多线程的好处。 (但是,我无法用测量数据支持这一点。)
老实说,那里有你的问题。找出性能基准并证明这是问题,或者花费数小时/天来解决完全错误的问题。
答案 1 :(得分:1)
你正在以一种巧妙的错误方式思考这个问题,这会导致编码非常糟糕。您不想等待线程,您希望等待工作完成。
可能最有效的方式是共享的,可等待的计数器。当您完成新工作时,递增计数器并向计数器发出信号。完成工作后,减少计数器。如果没有工作要做,请在柜台等候。如果将计数器降为零,请检查是否可以重新开始工作。
答案 2 :(得分:1)
如果我理解正确,你会试图将你的解决方案分解为多个部分并单独解决,但同时,对吧?然后让你当前的线程等待那些任务?你想使用像fork / join模式这样的东西。
List<CustomThread> threads = new ArrayList<CustomThread>();
for (Something something : somethings) {
threads.add(new CustomThread(something));
}
for (CustomThread thread : threads) {
thread.start();
}
for (CustomThread thread : threads) {
thread.join(); // Blocks until thread is complete
}
List<Result> results = new ArrayList<Result>();
for (CustomThread thread : threads) {
results.add(thread.getResult());
}
// do something with results.
在Java 7中,通过fork / join池提供更多支持。请参阅ForkJoinPool
和its trail,并使用Google查找其他许多教程之一。
你可以通过这个概念来获取你想要的树,只需让你创建的线程以完全相同的方式生成更多的线程。
编辑:我的印象是你不会创建那么多线程,所以这对你的场景更好。这个例子不会非常简短,但它与你在另一个答案中的讨论一样,你可以等待工作,而不是线程。
首先,对于需要Callable
并返回Input
的子作业,您需要Result
:
public class SubJob implements Callable<Result> {
private final Input input;
public MyCallable(Input input) {
this.input = input;
}
public Result call() {
// Actually process input here and return a result
return JobWorker.processInput(input);
}
}
然后使用它,创建一个具有固定大小的线程池的ExecutorService
。这将限制您同时运行的作业数量,因此您不会意外地对系统进行线程炸弹。这是你的主要工作:
public class MainJob extends Thread {
// Adjust the pool to the appropriate number of concurrent
// threads you want running at the same time
private static final ExecutorService pool = Executors.newFixedThreadPool(30);
private final List<Input> inputs;
public MainJob(List<Input> inputs) {
super("MainJob")
this.inputs = new ArrayList<Input>(inputs);
}
public void run() {
CompletionService<Result> compService = new ExecutorCompletionService(pool);
List<Result> results = new ArrayList<Result>();
int submittedJobs = inputs.size();
for (Input input : inputs) {
// Starts the job when a thread is available
compService.submit(new SubJob(input));
}
for (int i = 0; i < submittedJobs; i++) {
// Blocks until a job is completed
results.add(compService.take())
}
// Do something with results
}
}
这将允许您重复使用线程,而不是每次要运行作业时生成一堆新线程。完成服务将在等待作业完成时执行阻止。另请注意,results
列表将按顺序完成。
您还可以使用Executors.newCachedThreadPool
,这会创建一个没有上限的池(例如使用Integer.MAX_VALUE
)。如果一个可用,它将重用线程,如果池中的所有线程都在运行作业,则创建一个新线程。如果你开始遇到死锁,这可能是合乎需要的(因为固定线程池中有太多的作业在等待子作业无法运行和完成)。这至少会限制您正在创建/销毁的线程数。
最后,您需要手动关闭ExecutorService
,可能是通过关闭挂钩,或者它包含的线程不允许JVM终止。
希望有帮助/有意义。
答案 3 :(得分:0)
如果您有生成任务(如处理矩阵列的示例),那么您可能会遇到CyclicBarrier。也就是说,如果必须完成第1代的每一项工作以处理第2代的任何工作,那么你所能做的最好就是等待满足这个条件。
如果每一代中有数千个任务,那么最好将所有这些任务提交给ExecutorService(ExecutorService.invokeAll),并等待结果返回到继续下一步之前。这样做的好处是消除了上下文切换,并且当物理CPU受限时,浪费时间/内存来分配数百个线程。
如果你的任务不是世代的,而是更多的树状结构,其中只有一个子集需要在该子集上发生下一步之前完成,那么你可能想要考虑一个ForkJoinPool
和你不需要Java 7来做到这一点。您可以获得Java 6的参考实现。这可以在JSR引入ForkJoinPool库代码的任何内容中找到。
我还有另一个answer,它在Java 6中提供了一个粗略的实现:
public class Fib implements Callable<Integer> {
int n;
Executor exec;
Fib(final int n, final Executor exec) {
this.n = n;
this.exec = exec;
}
/**
* {@inheritDoc}
*/
@Override
public Integer call() throws Exception {
if (n == 0 || n == 1) {
return n;
}
//Divide the problem
final Fib n1 = new Fib(n - 1, exec);
final Fib n2 = new Fib(n - 2, exec);
//FutureTask only allows run to complete once
final FutureTask<Integer> n2Task = new FutureTask<Integer>(n2);
//Ask the Executor for help
exec.execute(n2Task);
//Do half the work ourselves
final int partialResult = n1.call();
//Do the other half of the work if the Executor hasn't
n2Task.run();
//Return the combined result
return partialResult + n2Task.get();
}
}
请记住,如果您将任务划分得太多并且每个线程完成的工作单元太小,则会对性能产生负面影响。例如,上面的代码是解决Fibonacci的非常缓慢的方法。