Java固定大小的线程池和所有CPU内核的最佳用法 - java

如何一直使用8个线程作为“昂贵”部件?

我遇到了一个数字紧缩问题,为此我创建了一个简单的框架。我的问题是找到一种优雅且简单的方法来最佳地使用所有CPU内核。

为了获得良好的性能,我使用固定大小为8的线程池。该想法是使用与硬件线程一样多的线程以获得最佳性能。

该框架的简化伪代码用法如下:

interface Task {
  data[] compute(data[]);
}

Task task = new Loop(new Chain(new DoX(), new DoY(), new Split(2, new DoZ())));
result = task.compute(data);

循环任务将循环直到满足某些终止条件
连锁任务将连锁任务(例如在上面的r = t1.compute(r); r = t2.compute(r); r = t3.compute(r);返回r;)
拆分任务会拆分数据并在零件上执行任务(例如,创建2个零件并返回新数据[] {t1.compute(part1),t1.compute(part2)})

目前,该线程已在“拆分任务”中实现。因此,拆分任务会将t1.compute(part1)和t1.compute(part2)的计算交给线程池。

方法1,可能完全死锁

我的第一种方法是拆分任务具有一系列期货,并依次调用get()。但这意味着,如果拆分任务位于另一个拆分任务中,则future.get()中的阻塞等待将阻塞外部拆分任务从线程池中获取的线程。所以我只有不到8个线程真正在工作。如果这个层次很深,我可能没有人在工作,永远等待。

1)我假设future.get()不会将线程返回线程池,对吗?因此,如果那样做,我将在future.get()中等待,但是没有更多线程可以开始工作了吗? [我不能轻易测试,因为我已经改变了方法]

方法2,目前,至少有人在工作

我当前的方法(不是更好)是使用当前线程来完成拆分的最后一部分(partN)。如果完成,我将检查partN-1是否已经启动,如果是,我将等待future.get()的所有任务。否则,当前线程也会执行partN-1,如果需要的话,也会执行partN-2 ...所以现在我应该总是在池中至少有一个线程在工作。

但是,由于问题1)的答案可能是future.get()会阻塞我的线程,因此采用这种方法,我在深度层次结构上只有很少的工作线程。

方法3,我看到的唯一解决方案

我假设我必须使用2个线程池,一个用于辛苦工作,一个用于所有等待。因此,我将有一个固定大小的线程池用于艰苦的工作,(有一个动态的?)用于等待。

3.a .:但这意味着拆分任务只能从等待池中产生线程,而执行实际工作的任务将从工作池中产生新线程并等待其完成。丑陋,但应该可以。丑陋的原因是,目前整个线程支持全部在“拆分任务”中,但是使用此解决方案,其他艰苦工作的任务必须了解线程。

3.b .:另一种方法是Split产生工作线程,但是在split内部,每个等待必须由一个等待线程完成,而当前线程同时也要执行worker线程任务。这样,所有线程支持都在Split Task类中,但是我不确定如何实现。

2a)如何在不阻塞当前线程的情况下等待任务?

2b)我可以将当​​前线程返回到工作线程池中,让一个等待者线程等待,然后在等待之后继续使用先前的当前线程或该工作池中的线程吗?怎么样?

其他解决方案

不要使用固定大小的线程池。

3)我的想法是有8个线程错误吗?但是,如果层次结构可以很深,那么又有多少呢? JVM是否不存在并行启动许多任务并在它们之间进行大量切换的风险?

4)我想念什么,或者您将如何解决该问题?

非常感谢和问候

[编辑]

接受的解决方案以及我为什么尝试其他尝试(基于方法2)

我接受了ForkJoinPool作为正确的解决方案。

但是,一些细节以及可能的开销和失控使我想尝试另一种方法。但是我想得越多,我就越多地使用ForkJoinPool(原因请参见最后的注释)。抱歉,文本数量不多。

http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ForkJoinPool.html

“但是,面对阻塞的IO或其他不受管理的同步,无法保证此类调整。”

“正在运行的最大线程数为32767”

http://homes.cs.washington.edu/~djg/teachingMaterials/grossmanSPAC_forkJoinFramework.html

“ ForkJoin框架的文档建议创建并行子任务,直到基本计算步骤的数量超过100且少于10,000。”

“艰苦的工作”任务从磁盘读取了大量数据,与10,000个基本计算值相距甚远。实际上,我可以将其分叉/加入到可能可接受的水平,但是由于这部分代码相当复杂,因此现在工作量很大。

我认为方法3a基本上是ForkJoin的实现,除了我将拥有更多的控制权和可能更少的开销,并且上面提到的问题不应该存在(但不能自动适应OS提供的CPU资源,但是我将迫使OS进行操作)给我我想要的东西)。

我可能会尝试使用方法2进行一些更改:那样我可以使用确切的线程号,并且没有等待线程,如果我正确理解ForkJoinPool似乎可以使用等待线程。

当前线程会执行作业,直到此Split实例中的所有作业都由工作线程运行(因此,像以前一样在Split节点中进行工作窃取),但随后它将不调用future.get(),而只是检查是否所有期货都已准备就绪future.isDone()。如果还没有全部完成,它将从线程池中窃取作业并执行该作业,然后再次检查期货。这样,只要有一个作业没有运行,我就永远不会等待。

丑陋的人:如果没有要偷的工作,我将不得不睡一会儿,然后再次检查期货或从池中偷走新工作(是否有办法等待多个期货全部完成且超时)如果触发,将不会取消计算?)

因此,我认为我必须在每个拆分任务中为ThreadPool使用完成服务,然后我可以使用超时进行轮询,而无需休眠。

假设:完成服务中的ThreadPool仍可以像普通ThreadPool一样使用(例如,作业窃取)。一个ThreadPool可以位于许多完成服务中。

我认为这是针对问题中详细问题的最佳解决方案。但是,这有一个小问题,请参阅以下内容。

注意:

再次查看“困难”任务后,我发现可以对它们的许多实例进行并行化。因此,在那里添加线程也是下一步的逻辑步骤。这些始终是叶节点,它们的工作最好通过完成服务来完成(在某些情况下,子作业可以具有不同的运行时,但是任何两个结果都可以构建新的作业)。要使用ForkJoinPool来实现它们,我必须使用managedBlock()并实现ForkJoinPool.ManagedBlocker,这会使代码更加复杂。但是,与此同时,在这些离开节点中使用CompletionService意味着基于我的方法2的解决方案也可能也需要等待线程,所以我最好还是使用ForkJoinPool。

java大神给出的解决方案

您似乎有一个并行的“分而治之”类型的问题,在该问题中,您将问题递归地分解为多个子问题,以使用可用核“解决”。

您是正确的,创建线程的niave实现很可能会使用大量资源,而使用有限线程池很可能会导致死锁。

第三种选择是在Java 7中实现的“ fork / join”模型。这在Oracle Java教程(here)中进行了描述,但是我认为Dan Grossman的讲义在解释它方面做得更好:

Beginner's Introduction to Java's ForkJoin Framework-丹·格罗斯曼

java:继承 - java

有哪些替代继承的方法? java大神给出的解决方案 有效的Java:偏重于继承而不是继承。 (这实际上也来自“四人帮”)。他提出的理由是,如果扩展类未明确设计为继承,则继承会引起很多不正常的副作用。例如,对super.someMethod()的任何调用都可以引导您通过未知代码的意外路径。取而代之的是,持有对本来应该扩展的类的引用,然后委托给它。这是与Eric…

Swing-典型桌面应用程序中的并发 - java

这可能是一个广泛的问题,但我想知道单用户桌面应用程序通常在何处使用并发。您如何发现理想情况下(在设计阶段)并发问题?附注:从操作系统理论上来说,并发问题通常发生在无法共享资源(例如打印机)的情况下。不过,我仍然对在编程级别上可能发生的情况感到迷惑。 java大神给出的解决方案 Event Dispatch thread与其他逻辑之间的Swing中可能存在线程…

如何修改休眠的SQL查询? - java

我有点好奇,有没有办法修改hibernate的核心,以便我可以自定义生成的SQL query。例如,在生成的查询中添加功能以使用connect by prior(oracle)或我要自定义的任何其他子句。 java大神给出的解决方案 起初,这样的问题总是在我心中敲响警钟。你被警告了...AFAIK,hibernate使用所谓的dialects进行特定的优化。…

用Java构建大批量数据处理工具 - java

Closed. This question needs to be more focused。它当前不接受答案。 想改善这个问题吗?更新问题,使其仅通过editing this post专注于一个问题。 3年前关闭。 Improve this question 我正在尝试使用Java构建ETL工具。 ETL工具用于对大量数据(关系型和其他类型)进行批量读取,…

用Java封装对象? - java

private中的Java提供类级别的封装。可以封装一个对象吗?还是这样做徒劳?例如,如果我们将一个类定义为 public class Person { private String ssn; private ArrayList<Person> friends = new ArrayList<Person>(); public voi…