MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java CompletableFuture thenRun任务完成后的无参操作技巧

2022-06-055.1k 阅读

Java CompletableFuture thenRun任务完成后的无参操作技巧

在Java的异步编程中,CompletableFuture提供了强大的功能来处理异步任务的组合与执行。thenRun方法是CompletableFuture众多方法中的一员,它允许我们在一个CompletableFuture任务完成后执行一个无参数的后续操作。这在很多场景下都非常有用,比如在某个异步任务结束后进行一些清理工作、记录日志或者触发另一个不需要依赖前序任务结果的独立任务。

thenRun方法的基本使用

thenRun方法的定义如下:

public CompletableFuture<Void> thenRun(Runnable action)

它接受一个Runnable类型的参数,也就是一个无参无返回值的任务。当调用该方法的CompletableFuture任务正常完成(即没有抛出异常)时,会执行传入的Runnable任务。

下面通过一个简单的示例来展示其基本用法:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class ThenRunExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // 模拟一个异步任务
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "任务完成";
        });

        CompletableFuture<Void> result = future.thenRun(() -> {
            System.out.println("异步任务已完成,执行后续操作");
        });

        // 等待任务完成
        result.get();
    }
}

在上述代码中,我们首先通过CompletableFuture.supplyAsync创建了一个异步任务,该任务模拟了一个耗时2秒的操作,并返回字符串“任务完成”。然后我们调用thenRun方法,传入一个Runnable,当异步任务完成后,thenRun中的代码会被执行,打印出“异步任务已完成,执行后续操作”。

thenRun与任务链的关系

CompletableFuture的强大之处在于它可以构建复杂的任务链。thenRun方法在任务链中起到了衔接的作用,允许我们在一个任务完成后立即执行另一个不依赖于前序任务结果的任务。

例如,我们可以构建一个包含多个thenRun操作的任务链:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class ThenRunChainExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            // 第一个异步任务
            System.out.println("第一个异步任务开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第一个异步任务执行完毕");
        }).thenRun(() -> {
            // 第二个异步任务
            System.out.println("第二个异步任务开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第二个异步任务执行完毕");
        }).thenRun(() -> {
            // 第三个异步任务
            System.out.println("第三个异步任务开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第三个异步任务执行完毕");
        });

        future.get();
    }
}

在这个例子中,我们通过CompletableFuture.runAsync创建了第一个异步任务,然后通过连续调用thenRun方法,添加了两个后续的异步任务。每个任务依次执行,且后一个任务在前一个任务完成后立即开始。

thenRun与异常处理

虽然thenRun本身并不处理异常,但它会受到前序CompletableFuture任务异常的影响。如果调用thenRun方法的CompletableFuture任务抛出异常,那么thenRun中的Runnable任务将不会被执行。

以下是一个展示异常情况的示例:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class ThenRunWithExceptionExample {
    public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // 模拟一个异步任务,抛出异常
            throw new RuntimeException("任务执行出错");
        });

        CompletableFuture<Void> result = future.thenRun(() -> {
            System.out.println("异步任务已完成,执行后续操作");
        });

        result.exceptionally(ex -> {
            System.out.println("捕获到异常: " + ex.getMessage());
            return null;
        });

        try {
            result.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,CompletableFuture.supplyAsync创建的异步任务抛出了一个运行时异常。由于该异常的存在,thenRun中的Runnable任务不会被执行。我们通过exceptionally方法捕获了异常,并打印出异常信息。

thenRun与线程调度

thenRun方法执行的Runnable任务默认会在调用thenRun方法的线程池中执行。如果前序CompletableFuture任务是通过CompletableFuture.supplyAsyncCompletableFuture.runAsync创建的,那么默认会使用ForkJoinPool.commonPool()线程池。

我们可以通过自定义线程池来控制thenRun任务的执行线程。例如:

import java.util.concurrent.*;

public class ThenRunWithCustomThreadPool {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务在自定义线程池线程中执行: " + Thread.currentThread().getName());
            return "任务结果";
        }, executor);

        CompletableFuture<Void> result = future.thenRun(() -> {
            System.out.println("thenRun任务在自定义线程池线程中执行: " + Thread.currentThread().getName());
        }, executor);

        result.join();
        executor.shutdown();
    }
}

在这个例子中,我们创建了一个固定大小为3的线程池executorCompletableFuture.supplyAsync创建的异步任务以及thenRun中的任务都会在这个自定义线程池中执行。通过打印线程名称,我们可以验证这一点。

thenRun在实际项目中的应用场景

  1. 任务完成后的清理工作 在一些需要进行资源操作的异步任务中,比如文件读写、数据库连接等,当任务完成后,我们需要进行资源的清理。例如,在读取完文件后关闭文件流:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;

public class FileReadWithCleanup {
    public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new FileReader("example.txt"));
                return reader.readLine();
            } catch (IOException e) {
                throw new RuntimeException(e);
            } finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        future.thenRun(() -> {
            System.out.println("文件读取任务完成,资源已清理");
        });
    }
}
  1. 日志记录 在异步任务完成后记录任务的执行情况是一种常见的需求。例如,记录任务完成的时间、任务是否成功等信息:
import java.util.Date;
import java.util.concurrent.CompletableFuture;

public class TaskLoggingExample {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            // 模拟异步任务
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "任务结果";
        }).thenRun(() -> {
            System.out.println("任务于 " + new Date() + " 完成");
        });
    }
}
  1. 触发独立的后续任务 有时候,一个异步任务完成后,我们需要触发另一个与前序任务结果无关的独立任务。比如,在一个订单处理任务完成后,触发一个库存更新任务:
import java.util.concurrent.CompletableFuture;

public class OrderAndInventoryExample {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            // 模拟订单处理任务
            System.out.println("订单处理任务开始");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("订单处理任务完成");
            return "订单处理结果";
        }).thenRun(() -> {
            // 库存更新任务
            System.out.println("库存更新任务开始");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("库存更新任务完成");
        });
    }
}

thenRun与其他相关方法的比较

  1. thenRun vs thenAccept thenAccept方法接受一个Consumer类型的参数,它会在CompletableFuture任务完成后执行,并且可以访问前序任务的结果。而thenRun方法接受的是Runnable,不能访问前序任务的结果。例如:
import java.util.concurrent.CompletableFuture;

public class ThenRunVsThenAccept {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> "任务结果")
                .thenAccept(result -> System.out.println("接受任务结果: " + result))
                .thenRun(() -> System.out.println("不依赖任务结果的操作"));
    }
}

在这个例子中,thenAccept可以打印出前序任务的结果,而thenRun则执行一个与结果无关的操作。

  1. thenRun vs thenApply thenApply方法接受一个Function类型的参数,它会在CompletableFuture任务完成后执行,并返回一个新的CompletableFuture,新的CompletableFuture的结果是Function的返回值。而thenRun不返回任何结果,只是执行一个无参操作。例如:
import java.util.concurrent.CompletableFuture;

public class ThenRunVsThenApply {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> 10)
                .thenApply(result -> result * 2)
                .thenRun(() -> System.out.println("操作完成,但不返回结果"));
    }
}

在这个例子中,thenApply将前序任务的结果乘以2,而thenRun只是执行一个无返回值的操作。

thenRun方法的注意事项

  1. 异常处理:如前文所述,thenRun本身不处理异常,需要在任务链中通过exceptionally等方法进行异常捕获和处理,以确保程序的健壮性。
  2. 线程安全:如果thenRun中的Runnable任务涉及到共享资源的操作,需要注意线程安全问题,例如使用同步机制或者线程安全的类。
  3. 性能考虑:虽然CompletableFuture提供了强大的异步编程能力,但过多的异步任务和复杂的任务链可能会导致性能问题,尤其是在资源有限的情况下。需要合理规划任务的执行顺序和线程池的使用。

通过深入理解CompletableFuturethenRun方法及其在异步编程中的应用,我们可以更高效地编写异步代码,提高程序的性能和响应性。无论是在大型企业级应用还是小型项目中,掌握这些技巧都能为我们的开发工作带来很大的便利。希望本文的内容能帮助你更好地运用thenRun方法解决实际问题。