等待第一个线程完成后再并行运行其他线程

时间:2016-03-04 07:28:09

标签: java multithreading parallel-processing java-8

从我的主要流程中我想并行完成一些任务。假设我有4个任务要在单独的流程中执行,但在这4个任务中,第一个任务需要在我可以并行运行其他任务之前完成。

我认为我的代码按照我的要求运行,但是有更好的方法吗?

public static void main(String[] args) {
        System.out.println("In main");

        ExecutorService executor = Executors.newSingleThreadExecutor();

        executor.submit(() -> {
            runParalleltasks();
        });

        executor.shutdown();
        System.out.println("Exiting main");
    }

    private static void runParalleltasks() {
        System.out.println("Running parallel tasks");
        doTask1();
        ExecutorService executor = Executors.newFixedThreadPool(3);
        executor.submit(() -> {
            doTask2();
        });
        executor.submit(() -> {
            doTask3();
                });
        executor.submit(() -> {
            doTask4();
        });

        executor.shutdown();
        System.out.println("Exiting parallel tasks");
    }

2 个答案:

答案 0 :(得分:3)

您可能需要查看新的CompletableFuture工具。它可能不会像你这样的小案例提供很多帮助,但它提供了很大的灵活性:

import static java.util.concurrent.CompletableFuture.allOf;
import static java.util.concurrent.CompletableFuture.runAsync;

runAsync(() -> doTask1(), executor)
        .thenCompose(v -> allOf(
                runAsync(() -> doTask2(), executor),
                runAsync(() -> doTask3(), executor),
                runAsync(() -> doTask4(), executor)
        ));

如果你需要将task1的输出传递给依赖任务,那么这种方法真的会大放异彩。假设doTask1返回StringdoTask2接受String。现在我们应该使用runAsync来代替supplyAsync而不是supplyAsync(() -> doTask1(), executor) .thenCompose(resultOf1 -> allOf( runAsync(() -> doTask2(resultOf1), executor), runAsync(() -> doTask3(), executor), runAsync(() -> doTask4(), executor) ));

{
    "aps": {
         "badge": 10,
         "alert": "Hello world!",
         "sound": "cat.caf"
    }
}

答案 1 :(得分:2)

您也可以使用ExecutorService执行此操作,这与您的原始方法非常相似。您所要做的就是使用 submit的结果,这允许后续任务等待其完成:

ExecutorService executor = Executors.newFixedThreadPool(4);
Future<?> task1 = executor.submit(() -> doTask1());
Stream.<Runnable>of(() -> doTask2(), () -> doTask3(), () -> doTask4())
      .forEach(r -> executor.submit(() -> { try {
              task1.get();
              r.run();
          } catch(InterruptedException|ExecutionException ex){}
      }));
executor.shutdown();

我们必须在等待第一个任务完成时捕获异常,但如果第一个任务失败或被取消,这也有可能故意跳过下一个任务。由于通常情况下,任务之间的依赖关系存在,因为后续任务需要第一个任务的结果,通过get()等待是自然的方式。只要doTask1()返回一个值,上面的submit调用就会使用submit(Callable)而不是submit(Runnable),而返回的Future会有一个反映回报的泛型类型方法的类型,因此可以通过get()方法检索结果。

为了避免死锁,必须满足以下条件中的至少一个:

  • 所有提交的任务都有足够的线程,或
  • 队列的检索顺序与提交顺序相同

在示例中,即使两者都适用。

请注意,上面的流使用不是必需的,经典循环也可以这样做:

for(Runnable r: Arrays.<Runnable>asList(() -> doTask2(), () -> doTask3(), () -> doTask4()))
    executor.submit(() -> { try {
            task1.get();
            r.run();
        } catch(InterruptedException|ExecutionException ex){}
    });