使用有限线程的Java并发

时间:2011-01-02 05:10:54

标签: java multithreading concurrency directed-acyclic-graphs

问候Overflowers,

  • 数据结构是非循环树 任意数量的节点。
  • 较浅的节点依赖于 更深层节点的结果。
  • 最终结果可以简单 通过遍历树计算 递归。
  • 如果我有无限的线程,我会的 为每个节点分配一个线程或甚至 更多。
  • 分配给较浅节点的线程 会等待更深层次的节点 完成。
  • 但是,我只有有限的线程; 有时更多,有时甚至更少 总节点。

关于如何遍历这些树并最终以有限的线程获得最终结果的任何想法?

此致

4 个答案:

答案 0 :(得分:3)

您想查看Topological Ordering有向无环图(DAG)。

如果图中的节点表示“作业”,则DAG的拓扑排序为您提供必须完成的顺序。维基百科页面具有到达订单的算法。

订单完成后,您的工作线程将开始从此订购中消耗“作业”(元素)。在开始作业之前,线程需要检查依赖作业是否已完成,但它们应该已完成,或者正在由另一个线程进行。

由于你有一个树形结构,你可能会遇到一些特殊情况:简单地将子节点放在第一位,然后将它们的父节点等放在另一个树的每个级别上。

此外,在问题上抛出“无限”的线程数会引起人们的注意......除非您的作业通常受I / O限制,否则(CPU数量+某些常量)线程似乎是合适的。 / p>

答案 1 :(得分:1)

  1. 你应该总是考虑使用线程池,而不是无限制的线程数。
  2. 类库提供了灵活的线程池实现以及一些有用的预定义配置。您可以通过调用Executors中的一个静态工厂方法来创建线程池。我认为对于你的情况应该使用以下方法:

    newFixedThreadPool - 创建一个固定大小的线程池,在提交任务时创建线程,最大池大小,然后尝试保持池大小不变(如果线程因意外异常而死亡,则添加新线程)。

    在创建线程池期间,您可以设置池大小,但是您可以根据需要向执行程序添加任意数量的线程(例如,每个节点的线程)。将不会执行的线程将排队。

  3. 如果您有一批计算要提交给执行人,并且您希望在结果可用时检索他们的结果,则可以使用完成服务。 CompletionService结合了Executor和BlockingQueue的功能。您可以向其执行可调用任务以执行,并使用队列,如方法take和poll来检索已完成的结果,打包为Futures,因为它们可用。 ExecutorCompletionService实现CompletionService,将计算委托给Executor。

    以下是在Concurrency book中使用Java中的CompletionService的示例:

    public class Renderer {
        private final ExecutorService executor;
    
    
    
    Renderer(ExecutorService executor) { this.executor = executor; }
    
    
    void renderPage(CharSequence source) {
        final List<ImageInfo> info = scanForImageInfo(source);
        CompletionService<ImageData> completionService =
            new ExecutorCompletionService<ImageData>(executor);
        for (final ImageInfo imageInfo : info)
            completionService.submit(new Callable<ImageData>() {
                 public ImageData call() {
                     return imageInfo.downloadImage();
                 }
            });
    
    
        renderText(source);
    
    
        try {
            for (int t = 0, n =  info.size(); t < n;  t++) {
                Future<ImageData> f = completionService.take();
                ImageData imageData = f.get();
                renderImage(imageData);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (ExecutionException e) {
            throw launderThrowable(e.getCause());
        }
    }
    
    }

答案 2 :(得分:1)

对于CPU密集型任务,最佳线程数可能是您拥有的核心或逻辑线程数。这可能是您机器上的4,6或8。创建与可用物理线程数匹配的线程池的简单方法是。

ExecutorService pool = Executors.newFixedThreadPool(
                       Runtime.getRuntime().availableProcessors());

如果线程数多于核心数,则线程将不得不通过上下文切换增加开销并使其变慢。

答案 3 :(得分:1)

这看起来非常适合为Java 7开发的Fork / Join框架;它也适用于http://gee.cs.oswego.edu/dl/concurrency-interest/的Java 6(在jsr166y下)。您可以使用RecursiveTask类来表示计算,并为数据结构中的子节点分配其他任务。 http://download.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html有一个简短的教程。