Java如何实现并行,分工,动态?

时间:2016-06-21 19:39:58

标签: java multithreading

我是Java中的多线程新手。

我的目标是让一个线程读取一个文件,然后将工作块传递给工作线程并行处理。

这里有一个非常好的例子。 Concurrency Tutorial

此代码段获取工作列表(ArrayList<String> URLs)并将其转储到具有Task.call()方法中指定的函数的工作线程块中。

void pingAndReportEachWhenKnown()  throws InterruptedException, ExecutionException  {
  int numThreads = URLs.size() > 4 ? 4 : URLs.size(); //max 4 threads
  ExecutorService executor = Executors.newFixedThreadPool(numThreads);
  CompletionService<PingResult> compService = new ExecutorCompletionService<>(executor);
  for(String url : URLs){
    Task task = new Task(url);
    compService.submit(task);
  }
  for(String url : URLs){
    Future<PingResult> future = compService.take();
    log(future.get());
  }
  executor.shutdown(); //always reclaim resources
}

这正是我想要做的,但我需要改变。 我的工作队列的大小不适合工作内存(HUGE文件),所以我需要缓冲读取行。我可以使用ArrayBlockingQueue实现阻止。但是我还需要将任务的分配缓冲到CompletionService。工作块大小会有所不同,因此完成时间也会有所不同。

我怎么不在compService工作队列上放太多钱?以下代码将一次放置一个项目,因为它会在尝试从队列中获取另一个任务之前等待完成。所以这还不够。处理这个问题的正确或最佳方法是什么?

for(;;){
  Task task = arrayBlockingQueue.take();  //Blocking operation 
  compService.submit(task);
  Future<PingResult> future = compService.take();  //Blocking operation
  log(future.get());
}

4 个答案:

答案 0 :(得分:2)

您可以直接创建Executors.newFixedThreadPool(numThreads),而不是调用ThreadPoolExecutor。该类的一个构造函数允许您提供线程池将使用的队列。

因此,为它提供一个有界队列(例如,一个具有固定容量的ArrayBlockingQueue):当队列已满时,您的生产者线程将被阻止,并且它将停止读取该文件,直到某些工作已经完成。

John Vint说,

  

不幸的是,这不会奏效。一旦队列已满并且所有线程都忙,将抛出RejectedExecutionException。

如果您使用此构造函数,该怎么办?

ExecutorService executorService = new ThreadPoolExecutor(
    CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit...,
    new ArrayBlockingQueue<>(TASK_BACKLOG_LIMIT),
    new ThreadPoolExecutor.CallerRunsPolicy()
    );

ThreadPoolExecutor的javadoc讨论当任务被拒绝时会发生什么(例如,因为队列已满)。它说默认行为是抛出RejectedExecutionException,但是......

  

...提供了四个预定义的处理程序策略...(2)在ThreadPoolExecutor.CallerRunsPolicy中,调用execute的线程本身运行任务。这提供了一种简单的反馈控制机制,可以降低新任务的提交速度......

答案 1 :(得分:1)

之前我做了类似的事情,你可以使用一个BufferedReader并在线程读取后设置一个阈值线程,将一个StringBuffer发送给线程进行处理。

另一个选择是将文件拆分为多个较小的文件。创建一个线程后,将每个文件发送给它。

以下是将大文件拆分为多个较小文件进行处理的示例。 Splitter是一个类,它只从较大的文件中抓取一定数量的行,放入较小的文件中。

    private void execute() {

    File segFile;
    Splitter split = new Splitter(maxLines, file);

    while ((segFile = split.nextSegment()) != null) {

        String seg = segFile.getName().substring(segFile.getName().lastIndexOf("_")+1);

        Runnable thread;

        if (workflow.equals("Account")) {
            thread = new AccountThread(segFile);
        }
        else {
            thread = new CustomerThread(segFile);
        }

        pool.execute(thread);
    }
    pool.shutdown();

    while (!pool.isTerminated()) {}

}

答案 2 :(得分:1)

据我所知,没有令人信服的理由来区分工作 - 生产者和工人线程。您可以简单地使读取操作成为线程安全的。

,而不是使用ArrayBlockingQueue
public static class SynchronizedBufferedReader extends BufferedReader {
    public SynchronizedBufferedReader(final Reader in) {
        super(in);
    }

    @Override
    public synchronized String readLine() throws IOException {
        return super.readLine();
    }
}

而不是每个任务产生一个结果,每个任务可以使用相同的Reader,并在for循环中工作,直到readLine返回null。这样,您可以创建与线程一样多的任务,并且所有任务都将保持忙碌状态。

答案 3 :(得分:0)

我认为你真正想要的是一个信号量。您可以获取资源并确保任务在完成后将其释放。这应该给你你想要的限制。

如果您在CompletableFuture中使用此代码,则应该使用更清晰的代码。

Semaphore semaphore = new Semaphore(NUMBER_OF_QUEUED_TASKS);
ExecutorService executor = Executors.newFixedThreadPool(numThreads);

for (String url : URLs) {
    semaphore.acquire(); // if there have been too many requests     
                         // queued wait until one is released

    CompletableFuture
        .supplyAsync(new Task(url), e)
        .thenAccept(this::log)
        .thenAccept((t) -> semaphore.relase(1));
}
e.shutdown();
...
public static class Task implements Supplier<PingResult> {
    @Override
    public PingResult get() {
    }
}