MST

星途 面试题库

面试题:Java异步编程下如何处理多个异步任务的依赖关系

假设有一系列异步任务,任务B依赖任务A的结果,任务C又依赖任务B的结果,且任务D与任务C并行执行,但任务E需要等待任务D和任务C都完成后才能执行。请使用Java的异步编程方式来实现这个逻辑,并说明可能遇到的问题及解决方案。
48.1万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

代码实现

import java.util.concurrent.*;

public class AsyncTasks {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(4);

        // 任务A
        Future<Integer> taskA = executorService.submit(() -> {
            // 模拟任务A的执行
            TimeUnit.SECONDS.sleep(1);
            System.out.println("Task A completed");
            return 10;
        });

        // 任务B依赖任务A的结果
        Future<Integer> taskB = executorService.submit(() -> {
            try {
                int resultA = taskA.get();
                // 模拟任务B的执行
                TimeUnit.SECONDS.sleep(1);
                System.out.println("Task B completed");
                return resultA * 2;
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                return -1;
            }
        });

        // 任务C依赖任务B的结果
        Future<Integer> taskC = executorService.submit(() -> {
            try {
                int resultB = taskB.get();
                // 模拟任务C的执行
                TimeUnit.SECONDS.sleep(1);
                System.out.println("Task C completed");
                return resultB + 5;
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                return -1;
            }
        });

        // 任务D与任务C并行执行
        Future<Integer> taskD = executorService.submit(() -> {
            // 模拟任务D的执行
            TimeUnit.SECONDS.sleep(1);
            System.out.println("Task D completed");
            return 20;
        });

        // 任务E需要等待任务D和任务C都完成后才能执行
        Future<Integer> taskE = executorService.submit(() -> {
            try {
                int resultC = taskC.get();
                int resultD = taskD.get();
                // 模拟任务E的执行
                TimeUnit.SECONDS.sleep(1);
                System.out.println("Task E completed");
                return resultC + resultD;
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                return -1;
            }
        });

        try {
            System.out.println("Task E result: " + taskE.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

可能遇到的问题及解决方案

  1. 线程池资源耗尽
    • 问题:如果同时提交大量异步任务,可能导致线程池中的线程全部被占用,新任务无法立即执行,从而影响系统性能。
    • 解决方案:合理设置线程池的大小,根据系统的硬件资源(如CPU核心数、内存等)和任务的特性(如I/O密集型还是CPU密集型)来调整线程池的核心线程数和最大线程数。例如,对于CPU密集型任务,线程池大小可以设置为CPU核心数;对于I/O密集型任务,可以适当增大线程池大小。
  2. 任务执行异常处理
    • 问题:在异步任务执行过程中,如果某个任务抛出异常,可能会导致后续依赖该任务结果的任务无法正确执行,且异常可能不会及时被发现。
    • 解决方案:在每个任务中使用try - catch块捕获异常,并进行适当的处理,如记录日志、返回默认值等。在获取任务结果时,也需要处理InterruptedExceptionExecutionException,以便及时发现和处理任务执行过程中的异常。
  3. 死锁问题
    • 问题:如果任务之间的依赖关系设计不当,可能会出现死锁。例如,任务A等待任务B的结果,而任务B又等待任务A的结果。
    • 解决方案:仔细设计任务之间的依赖关系,确保不存在循环依赖。在复杂的依赖场景下,可以使用有向无环图(DAG)来表示任务依赖关系,在提交任务前检查是否存在环,以避免死锁。
  4. 资源竞争
    • 问题:如果多个异步任务共享某些资源(如文件、数据库连接等),可能会出现资源竞争问题,导致数据不一致或程序出错。
    • 解决方案:对共享资源的访问进行同步控制,可以使用synchronized关键字、Lock接口等方式来保证同一时间只有一个任务可以访问共享资源。另外,也可以考虑使用线程安全的资源访问方式,如使用线程池提供的线程本地变量(ThreadLocal)来避免资源竞争。