提高逐行读取文件的性能并处理

时间:2018-06-01 00:04:29

标签: java multithreading executorservice long-running-processes

我有一段java代码,它执行以下操作 -

  1. 打开一个文件格式为{A,B,C}的文件,每个文件大约有。 5000000行。
  2. 对于文件中的每一行,调用一个提供D列的服务,并将其作为{A,B,C,D}附加到{A,B,C}。
  3. 将此条目写入一个chunkedwriter,最终将10000行组合在一起以将块写回远程位置
  4. 现在代码需要32个小时才能执行。这个过程将再次在另一个文件中重复,假设需要另外32个小时,但我们需要每天运行这些过程。

    第2步更复杂的是,有时服务没有D但是设计用于从其超级数据存储中获取D,因此它会抛出一个瞬态异常,要求您等待。我们已重试处理此问题,因此技术上可以重试5次,最大延迟为60000毫秒。因此,在最坏的情况下,我们可能会看到5000000 * 5。

    {A,B,C}的组合是唯一的,因此结果D不能被缓存和重复使用,并且必须每次都有新的请求来获得D.

    我尝试过添加这样的话:

    temporaryFile = File.createTempFile(key, ".tmp");
    Files.copy(stream, temporaryFile.toPath(), 
           StandardCopyOption.REPLACE_EXISTING);
    reader = new BufferedReader(new InputStreamReader(new 
           FileInputStream(temporaryFile), StandardCharsets.UTF_8));
    String entry;
    while ((entry = reader.readLine()) != null) {
       final String finalEntry = entry;
       service.execute(() -> {
             try {
                 processEntry(finalEntry);
             } catch (Exception e) {
                 log.error("something");
       });
       count++;
     }
    

    这里processEntry方法抽象出上面解释的实现细节,线程定义为

    ExecutorService service = Executors.newFixedThreadPool(10);
    

    我遇到的问题是第一组线程旋转但该过程不会等到所有线程完成其工作并且所有5000000线都完成。因此,过去等待完成32小时的任务现在以<1分钟结束,这会影响我们系统的状态。有没有其他方法可以做到这一点?如何让所有线程完成进程等待?

3 个答案:

答案 0 :(得分:1)

  • 考虑使用ExecutorCompletionService,如果要完成任务,则需要ExecutorCompletionService。这充当了BlockingQueue,允许您在完成任务时轮询它们。
  • 另一个解决方案是等待执行程序终止,然后使用以下命令将其关闭: ExecutorService service = Executors.newFixedThreadPool(10); service .shutdown(); while (!service .isTerminated()) {}

答案 1 :(得分:0)

另一种方法是在关闭主线程上的执行程序之前使用锁存器等待所有任务完成。

使用1.初始化CountdownLatch 退出提交任务的循环后,调用latch.await();

在您开始的任务中,您必须在起始类上进行回调,以便在任务完成时通知它。

请注意,在起始类中,必须同步回调函数。

在起始类中,您使用此回调来计算已完成的任务。

同样在回调中,当所有任务都完成后,你调用latch.countdown()让主线程继续,让我们说,关闭执行程序并退出。

这显示了主要概念,如果需要,可以更详细地实施,并对已完成的任务进行更多控制。

这将是这样的:

public class StartingClass {


    CountDownLatch latch = new CountDownLatch(1);

    ExecutorService service = Executors.newFixedThreadPool(10);
    BufferedReader reader;
    Path stream;
    int count = 0;
    int completed = 0;
    public void runTheProcess() {
        File temporaryFile = File.createTempFile(key, ".tmp");
        Files.copy(stream, temporaryFile.toPath(), 
               StandardCopyOption.REPLACE_EXISTING);
        reader = new BufferedReader(new InputStreamReader(new 
               FileInputStream(temporaryFile), StandardCharsets.UTF_8));
        String entry;
        while ((entry = reader.readLine()) != null) {
           final String finalEntry = entry;
           service.execute(new Task(this,finalEntry));
           count++;
        }
        latch.await();
        service.shutdown();
    }

    public synchronized void processEntry(String entry) {

    }

    public synchronized void taskCompleted() {
        completed++;
        if(completed == count) {
            latch.countDown();;
        }
    }

    //This can be put in a different file.
    public static class Task implements Runnable {
        StartingClass startingClass;
        String finalEntry;

        public Task(StartingClass startingClass, String finalEntry) {
            this.startingClass = startingClass;
            this.finalEntry = finalEntry;
        }

        @Override
        public void run() {
            try {
                startingClass.processEntry(finalEntry);
                startingClass.taskCompleted();
             } catch (Exception e) {
                 //log.error("something");
             }; 
        }

    }

}

请注意,您需要关闭该文件。此外,执行程序的删除可以写入等待几秒钟,然后强制关闭。

答案 2 :(得分:0)

  

我遇到的问题是第一组线程启动但是进程不会等到所有线程完成其工作并且所有5000000行都完成。

使用ExecutorService运行作业时,它们会添加到服务中并在后台运行。要等待它们完成,您需要等待服务终止:

ExecutorService service = Executors.newFixedThreadPool(10);
// submit jobs to the service here
// after the last job has been submitted, we immediately shutdown the service
service.shutdown();
// then we can wait for it to terminate as the jobs run in the background
service.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);

另外,如果这些文件中有大量的行,我建议您使用 bounded 队列来处理作业,这样就不会有效地释放内存文件中的行。这仅在文件保持不变并且不会消失时才有效。

// this is the same as a newFixedThreadPool(10) but with a queue of 100
ExecutorService service = new ThreadPoolExecutor(10, 10,
                            0L, TimeUnit.MILLISECONDS,
                            new LinkedBlockingQueue<Runnable>(100));
// set a rejected execution handler so we block the caller once the queue is full
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        try {
            executor.getQueue().put(r);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return;
        }
    }
});
  

将此条目写入一个chunkedwriter,最终将10000行组合在一起以将块写回远程位置

当每个A,B,C作业完成时,如果需要在第二步中处理,那么我还建议查看ExecutorCompletionService,它允许您将各种不同的线程池链接在一起,以便线条完成他们将立即开始处理第二阶段的工作。

如果相反,这个chunkedWriter只是一个线程,那么我建议共享一个BlockingQueue<Result>并在完成这些行并且chunkedWriter采用后将执行程序线程放入队列从队列中进行分块和写入结果。在这种情况下,向作者线程指示它已完成需要小心处理 - 可能是由主线程等待服务终止的某种END_RESULT常量放入队列。