Bootstrap

「Java」《深入解析Java多线程编程利器:CompletableFuture》

一、 引言

1. 对多线程编程的需求和挑战的介绍

多线程编程是指在一个程序中同时执行多个线程来提高系统的并发性和响应性。在现代计算机系统中,多线程编程已经成为开发者日常工作的一部分。以下是对多线程编程需求和挑战的介绍:

  1. 需求:
    • 提高系统的性能:通过同时执行多个线程,可以利用多核处理器的优势,实现任务的并行执行,从而提高系统的处理速度和吞吐量。
    • 改善用户体验:多线程编程可以使图形界面或交互式应用程序更加流畅和响应,避免长时间的等待和阻塞。
    • 实现后台任务:多线程可以用于执行后台任务,如数据加载、网络通信、文件写入等,提高用户界面的反应速度,同时保持后台任务的进行。
  1. 挑战:
  • 线程同步与竞态条件:多个线程共享同一份资源时,可能会引发问题,如竞态条件、死锁和数据不一致等。需要合理地使用锁、同步机制和线程安全的数据结构来保证线程之间的正确协作。
  • 上下文切换开销:在多线程环境下,线程的调度和切换会带来一定的开销,影响系统性能。合理控制线程数量和避免过度的上下文切换是关键。
  • 调试和测试困难:多线程程序的调试和测试相对复杂,由于线程间的异步执行和并发性,可能会导致问题不易重现和定位。
  • 安全性和可靠性:多线程编程需要考虑并发访问共享资源的问题,如果处理不当,可能会导致数据不一致、内存泄漏等安全问题。同时还需保证线程的稳定性、可靠性和正确性。

了解这些需求和挑战对于进行有效的多线程编程至关重要。开发者需要熟悉并掌握多线程编程的基本概念、技术和最佳实践,以解决并发编程中的问题,并正确地利用多线程来满足系统的需求。

2. 介绍CompletableFuture的作用和优势

CompletableFuture是Java 8引入的一个强大的多线程编程工具,用于处理异步任务和并发编程。它提供了更简洁、灵活和易用的方式来处理并发操作。以下是CompletableFuture的作用和优势的介绍:

  1. 异步任务处理:CompletableFuture可以用于执行异步任务,并在任务完成时获取结果。它可以帮助开发者更方便地处理耗时的操作,避免阻塞主线程。

  2. 组合多个任务:CompletableFuture可以将多个异步任务进行链式组合,实现任务之间的顺序关系。通过一系列的方法调用,可以实现任务的串行执行、并行执行以及任务间的依赖关系。

  3. 回调函数处理:CompletableFuture支持回调函数的方式处理异步任务的结果。可以在任务完成后执行相应的回调函数,进行后续操作,例如数据处理、结果分析等。

  4. 异常处理:CompletableFuture提供了灵活的异常处理机制。可以使用exceptionally()方法或handle()方法来处理任务中的异常情况,保证程序的健壮性和稳定性。

  5. 取消和超时处理:CompletableFuture支持任务的取消和超时处理。可以设置任务的执行时间上限,如果任务无法在规定时间内完成,可以进行相应的处理操作,避免长时间的等待和占用资源。

  6. 并发限制:CompletableFuture允许开发者设置任务的并发限制,控制同时执行的任务数量。这对于任务有资源限制或对并发度有要求的场景非常有用。

  7. 整合Stream操作:CompletableFuture可以与Java 8中引入的Stream API进行无缝整合。通过CompletableFuture的一些方法,可以在流中实现并行操作,提高处理效率。

CompletableFuture作为Java多线程编程的利器,使得异步任务和并发编程变得更加直观和简单。它提供了丰富的方法来处理并发操作,并具有灵活的异常处理、任务组合和回调处理能力。使用CompletableFuture可以大大简化多线程编程的复杂性,提高开发效率和程序性能。

二. CompletableFuture简介

1. CompletableFuture是Java中提供的一个强大的多线程编程工具

CompletableFuture是Java中提供的一个强大的多线程编程工具。它位于java.util.concurrent包下,是Java 8引入的一种Future的扩展形式,用于处理异步任务和并发编程。

CompletableFuture提供了一种更简洁、灵活和易用的方式来处理异步任务。它支持链式调用和函数式编程的风格,使得编写异步代码变得更加直观和方便。

通过CompletableFuture,可以完成以下操作:

  1. 异步执行:使用supplyAsync()runAsync()方法可以将任务提交到线程池中异步执行,这样就不会阻塞主线程。

  2. 链式操作:通过一系列的方法调用,可以将多个CompletableFuture组合在一起,形成一个任务链。例如,使用thenApply()thenAccept()thenCompose()等方法可以定义任务之间的依赖关系和后续操作。

  3. 异常处理:CompletableFuture提供了异常处理的机制,可以使用exceptionally()handle()whenComplete()等方法来处理异常情况,并在任务完成时执行相应的操作。

  4. 合并多个任务:使用allOf()anyOf()join()等方法可以将多个CompletableFuture进行合并和组合,实现对多个任务结果的处理。

  5. 取消和超时处理:CompletableFuture支持取消任务和设置超时时间,并提供了相应的方法来处理任务的取消和超时情况。

  6. 并发限制:可以使用CompletableFuture.supplyAsync().thenCombine()等方法来控制并发度,限制同时执行的任务数量。

CompletableFuture的引入大大简化了Java中的异步编程和并发处理,使得多线程编程变得更加方便和高效。它提供了丰富的操作方法和异常处理机制,帮助开发者更好地控制和组合异步任务,实现高效的并发编程。

2. 与传统的Thread和Runnable相比的优点

相对于传统的Thread和Runnable,CompletableFuture具有以下几个优点:

  1. 异步编程简单:CompletableFuture通过方法链的方式让异步编程变得更加直观和易于理解。开发者可以通过一系列的方法调用来组合和处理异步任务,而不需要手动管理线程和同步。

  2. 高级的任务组合:CompletableFuture提供了丰富的方法来组合多个任务,例如在一个任务完成后执行下一个任务、组合多个任务的结果等。这种链式调用的方式使得任务之间的关系更加清晰和灵活。

  3. 异常处理方便:CompletableFuture提供了专门的方法来处理异常情况。通过exceptionally()handle()whenComplete()等方法,可以更容易地捕获和处理任务中出现的异常。

  4. 取消和超时处理:CompletableFuture支持任务的取消和设置超时时间。可以使用cancel()方法取消任务,或者使用completeOnTimeout()方法设置任务的超时时间。这些功能在处理需要限时操作或在某些条件下需要中止任务的场景非常有用。

  5. 非阻塞主线程:CompletableFuture的任务是在一个线程池中执行的,因此不会阻塞主线程。这允许主线程继续执行其他操作,提高了应用程序的响应性能。

  6. 并发度控制:CompletableFuture提供了方式来控制任务的并发度。可以使用thenComposeAsync()thenCombineAsync()等方法来指定异步任务在多个线程上并发执行,从而提高性能。

  7. 整合Stream API:CompletableFuture可以与Java 8中引入的Stream API无缝集成。这意味着可以在流中使用CompletableFuture来进行并行操作,进一步简化了代码的编写和处理。

总的来说,CompletableFuture相对于传统的Thread和Runnable提供了更高级、更灵活、更易于使用的异步编程解决方案。它让异步任务的编写和组合变得更加简单和直观,并提供了丰富的方法来处理异常、取消任务和控制并发度。这使得开发者能够更好地管理和利用多线程环境,提高应用程序的性能和可维护性。

三、基本用法

1.创建CompletableFuture对象的方式

创建CompletableFuture对象的方式有多种,可以根据实际需求选择适合的方式。以下是几种常见的创建CompletableFuture对象的方式:

  1. 使用CompletableFuture.supplyAsync()创建异步执行的CompletableFuture对象,该方法接收一个Supplier类型的参数,表示要执行的任务,并返回一个CompletableFuture对象。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 异步执行的任务逻辑
    return "Hello, CompletableFuture!";
});
  1. 使用CompletableFuture.runAsync()创建异步执行的CompletableFuture对象,该方法接收一个Runnable类型的参数,表示要执行的任务,并返回一个CompletableFuture对象。
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    // 异步执行的任务逻辑
    System.out.println("Hello, CompletableFuture!");
});
  1. 使用CompletableFuture.completedFuture()创建已完成的CompletableFuture对象,该方法接收一个数值、对象或null作为参数,返回一个已经完成的CompletableFuture对象。
CompletableFuture<String> completedFuture = CompletableFuture.completedFuture("Hello");
  1. 使用CompletableFuture.newIncompleteFuture()创建一个未完成的CompletableFuture对象,以后可以通过调用其它方法来完成该对象。
CompletableFuture<String> future = new CompletableFuture<>();
// 后续在适当的时机通过调用complete方法完成该CompletableFuture对象
future.complete("Hello, CompletableFuture!");
  1. 使用CompletableFuture.allOf()CompletableFuture.anyOf()静态方法创建组合的CompletableFuture对象。allOf()接收多个CompletableFuture对象作为参数,并返回一个新的CompletableFuture对象,该对象在所有输入的CompletableFuture对象都完成后才会完成。anyOf()类似,只要有任意一个输入的CompletableFuture对象完成,返回的对象就会完成。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "CompletableFuture");

CompletableFuture<Void> allOfFuture = CompletableFuture.allOf(future1, future2);
CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(future1, future2);

这些是创建CompletableFuture对象的常见方式,根据具体的业务需求和场景,可以选择适合的方式来创建和组合CompletableFuture对象,实现异步编程和并发处理。

2. 异步执行任务并返回结果

要异步执行任务并返回结果,可以使用CompletableFuture.supplyAsync()方法创建一个CompletableFuture对象,并将要执行的任务逻辑包装在一个Supplier函数中。这个Supplier函数会在异步执行的线程中被调用,并返回计算的结果。

以下是一个示例代码:

import java.util.concurrent.CompletableFuture;

public class AsyncTaskExample {
    public static void main(String[] args) {
        // 异步执行任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // 异步执行的任务逻辑
            return "Hello, CompletableFuture!";
        });

        // 当任务完成时获取结果
        future.thenAccept(result -> System.out.println("Result: " + result));

        // 阻塞主线程,使异步任务有足够的时间完成
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在上面的示例中,我们使用CompletableFuture.supplyAsync()方法创建了一个CompletableFuture对象,并在Supplier函数中定义了要异步执行的任务逻辑。通过thenAccept()方法,我们注册了一个回调函数,当任务完成时会获取到计算的结果,并打印输出。

需要注意的是,在这个示例中,我们使用Thread.sleep()方法阻塞了主线程一段时间,以确保异步任务有足够的时间完成。实际使用中,主线程可能会执行其他的操作,而不必主动等待异步任务完成。

当然,CompletableFuture还提供了许多其他的方法来处理异步任务的结果,例如使用thenApply()方法对结果进行转换,使用exceptionally()方法处理异常情况等。根据具体的需求,可以选择适合的方法来处理异步任务的结果。

3. 使用回调函数处理异步任务的结果

使用回调函数处理异步任务的结果是通过在CompletableFuture对象上注册回调函数来实现的。当异步任务完成时,回调函数会被执行,并传递任务的结果作为参数。

以下是一个示例代码,演示如何使用回调函数处理异步任务的结果:

import java.util.concurrent.CompletableFuture;

public class AsyncCallbackExample {
    public static void main(String[] args) {
        // 异步执行任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // 异步执行的任务逻辑
            return "Hello, CompletableFuture!";
        });

        // 注册回调函数处理任务结果
        future.thenAccept(result -> System.out.println("Result: " + result));

        // 阻塞主线程,使异步任务有足够的时间完成
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在上面的示例中,我们通过thenAccept()方法在CompletableFuture对象上注册了一个回调函数。当异步任务完成后,回调函数会被执行,并将任务的结果作为参数传递给回调函数。在这个示例中,回调函数只简单地打印出结果。

需要注意的是,回调函数会在异步任务完成的线程上执行。如果回调函数需要进行耗时的操作或者阻塞,可能会影响到其他任务的执行。因此,建议在回调函数中只处理轻量级的操作,避免阻塞或耗时的操作。

除了thenAccept()方法外,CompletableFuture还提供了其他的回调函数方法,如thenApply()用于对任务结果进行转换,exceptionally()用于处理异常情况等。根据需求,选择适合的回调函数方法来处理异步任务的结果。

四、组合多个CompletableFuture

1. thenCompose()方法的使用

thenCompose()方法是CompletableFuture类提供的一个方法,用于处理异步任务的结果。它接受一个Function参数,该函数将当前CompletableFuture的结果作为输入,并返回另一个CompletableFuture对象。这个返回的CompletableFuture对象表示一个新的异步任务,可以继续执行链式操作。

下面是使用thenCompose()方法的示例代码:

import java.util.concurrent.CompletableFuture;

public class ThenComposeExample {
    public static void main(String[] args) {
        // 异步执行第一个任务
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            // 第一个任务逻辑
            return "Hello";
        });

        // 使用thenCompose()方法处理第一个任务的结果,并执行第二个任务
        CompletableFuture<String> future2 = future1.thenCompose(result -> {
            // 第二个任务逻辑,基于第一个任务的结果
            return CompletableFuture.supplyAsync(() -> result + ", CompletableFuture!");
        });

        // 当第二个任务完成时获取结果
        future2.thenAccept(result -> System.out.println("Result: " + result));

        // 阻塞主线程,使异步任务有足够的时间完成
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在上面的示例中,我们首先使用CompletableFuture.supplyAsync()方法创建了一个异步任务future1,它简单地返回字符串"Hello"。接着,我们使用thenCompose()方法处理future1的结果,根据第一个任务的结果执行第二个任务。第二个任务通过CompletableFuture.supplyAsync()方法创建,它会将第一个任务的结果与字符串", CompletableFuture!"拼接在一起。

最后,我们使用thenAccept()方法注册了一个回调函数,在第二个任务完成时打印结果。

需要注意的是,thenCompose()方法返回的是一个新的CompletableFuture对象,表示一个新的异步任务。通过这种方式,可以方便地链式执行多个异步任务,每个任务都依赖于上一个任务的结果。

在实际应用中,可以根据具体需求来组合和处理异步任务的结果,使用thenCompose()方法来进行任务的串联和组合。

2. thenCombine()方法的使用

thenCombine()方法是CompletableFuture类提供的一个方法,用于将两个独立的异步任务的结果进行合并处理。它接受两个CompletionStage参数和一个BiFunction参数,该函数将两个任务的结果作为输入,并返回一个新的结果。

下面是使用thenCombine()方法的示例代码:

import java.util.concurrent.CompletableFuture;

public class ThenCombineExample {
    public static void main(String[] args) {
        // 异步执行第一个任务
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            // 第一个任务逻辑
            return "Hello";
        });

        // 异步执行第二个任务
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            // 第二个任务逻辑
            return "CompletableFuture!";
        });

        // 使用thenCombine()方法处理两个任务的结果
        CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (result1, result2) -> {
            // 合并处理两个任务的结果
            return result1 + ", " + result2;
        });

        // 当合并任务完成时获取结果
        combinedFuture.thenAccept(result -> System.out.println("Result: " + result));

        // 阻塞主线程,使异步任务有足够的时间完成
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在上面的示例中,我们首先分别创建了两个独立的异步任务future1future2,它们分别返回字符串"Hello"和"CompletableFuture!"。然后,我们使用thenCombine()方法将这两个任务的结果进行合并处理。合并的逻辑由BiFunction参数指定,它将两个任务的结果拼接起来。

最后,使用thenAccept()方法注册了一个回调函数,在合并任务完成时打印结果。

需要注意的是,thenCombine()方法返回的是一个新的CompletableFuture对象,表示一个新的异步任务。通过这种方式,可以将多个独立的异步任务的结果合并在一起,并进行后续的处理。

在实际应用中,可以根据具体需求选择使用thenCombine()方法来处理多个异步任务的结果的合并操作。

3. allOf()和anyOf()方法的使用

allOf()anyOf()方法都是CompletableFuture类提供的静态方法,用于处理多个异步任务的结果。

allOf()方法接受一个可变参数,表示一组CompletableFuture对象,返回一个新的CompletableFuture对象。这个新的CompletableFuture对象表示一个新的异步任务,当所有输入的任务都完成时,它将完成。

下面是使用allOf()方法的示例代码:

import java.util.concurrent.CompletableFuture;

public class AllOfExample {
    public static void main(String[] args) {
        // 定义一组异步任务
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1");
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");
        CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Task 3");

        // 使用allOf()方法等待所有任务完成
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2, future3);

        // 当所有任务完成时执行回调函数
        allFutures.thenRun(() -> System.out.println("All tasks completed."));

        // 阻塞主线程,使异步任务有足够的时间完成
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在上面的示例中,我们定义了三个异步任务future1future2future3,它们分别返回字符串"Task 1"、“Task 2"和"Task 3”。然后,我们使用allOf()方法等待所有任务完成,并返回一个CompletableFuture<Void>对象。我们可以通过这个对象注册一个回调函数,在所有任务完成时打印一条消息。

需要注意的是,allOf()方法返回的CompletableFuture对象不关心每个任务的具体结果,只关心所有任务的完成情况。

anyOf()方法接受一个可变参数,表示一组CompletableFuture对象,返回一个新的CompletableFuture对象。这个新的CompletableFuture对象表示一个新的异步任务,当任意一个输入的任务完成时,它将完成。

下面是使用anyOf()方法的示例代码:

import java.util.concurrent.CompletableFuture;

public class AnyOfExample {
    public static void main(String[] args) {
        // 定义一组异步任务
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1");
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");
        CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> "Task 3");

        // 使用anyOf()方法等待任意一个任务完成
        CompletableFuture<Object> anyFuture = CompletableFuture.anyOf(future1, future2, future3);

        // 当任意一个任务完成时执行回调函数
        anyFuture.thenAccept(result -> System.out.println("One task completed: " + result));

        // 阻塞主线程,使异步任务有足够的时间完成
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在上面的示例中,我们同样定义了三个异步任务future1future2future3,它们分别返回字符串"Task 1"、“Task 2"和"Task 3”。然后,我们使用anyOf()方法等待任意一个任务完成,并返回一个CompletableFuture<Object>对象。我们可以通过这个对象注册一个回调函数,在任意一个任务完成时打印其结果。

需要注意的是,anyOf()方法返回的CompletableFuture对象只会关注第一个完成的任务,不会等待其他任务的完成。

通过使用allOf()anyOf()方法,可以方便地处理多个异步任务的结果,并根据不同的需求进行相应的处理。

五、异常处理

1. exceptionally()方法的使用

exceptionally() 方法是 CompletableFuture 类提供的一个方法,它允许你在异步任务抛出异常时提供一个默认的返回值或进行异常处理。该方法接受一个函数作为参数,这个函数会在异步任务抛出异常时被调用。

下面是 exceptionally() 方法的使用示例:

import java.util.concurrent.CompletableFuture;

public class ExceptionallyExample {
    public static void main(String[] args) {
        // 定义一个异步任务
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            // 任务逻辑,这里会抛出异常
            throw new RuntimeException("Task failed!");
        });

        // 使用 exceptionally() 方法处理异步任务的异常
        CompletableFuture<Integer> result = future.exceptionally(ex -> {
            // 在异常发生时处理异常并提供默认返回值
            System.out.println("Exception occurred: " + ex.getMessage());
            return 0;
        });

        // 等待异步任务完成并获取最终结果
        int value = result.join();
        System.out.println("Result: " + value);
    }
}

在上面的示例中,我们定义了一个异步任务 future,它会抛出一个运行时异常。然后,我们使用 exceptionally() 方法来处理异常,并提供一个默认的返回值。在异常发生时,异常处理函数会被调用,打印异常信息,并返回 0。最后,我们使用 join() 方法等待异步任务完成,并获取最终结果。

需要注意的是,exceptionally() 方法返回的是一个新的 CompletableFuture 对象,它表示一个新的异步任务。这个新的任务在原始任务抛出异常时会被触发,并执行异常处理函数。

使用 exceptionally() 方法可以方便地处理异步任务的异常情况,提供默认的返回值或进行异常处理,从而保证程序的可靠性和稳定性。

2. handle()方法的使用

六、 CompletableFuture的进阶功能

1. CompletableFuture的取消和超时处理

handle() 方法是 CompletableFuture 类提供的一个方法,它可以在异步任务完成后对结果进行处理,无论是否出现异常。相比于 exceptionally() 方法,handle() 方法可以处理正常结果和异常情况。

handle() 方法接受一个函数作为参数,这个函数会在异步任务完成后被调用,并接收任务的结果作为输入参数。这个函数可以返回一个结果,作为最终的处理结果。

下面是 handle() 方法的使用示例:

import java.util.concurrent.CompletableFuture;

public class HandleExample {
    public static void main(String[] args) {
        // 定义一个异步任务
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            // 任务逻辑,这里可能会抛出异常
            return 10;
        });

        // 使用 handle() 方法处理异步任务的结果和异常
        CompletableFuture<String> result = future.handle((value, ex) -> {
            if (ex != null) {
                // 异常处理
                System.out.println("Exception occurred: " + ex.getMessage());
                return "Default Value";
            } else {
                // 正常结果处理
                return "Result: " + value;
            }
        });

        // 等待异步任务完成并获取最终结果
        String finalResult = result.join();
        System.out.println(finalResult);
    }
}

在上面的示例中,我们定义了一个异步任务 future,它会返回一个整数。然后,我们使用 handle() 方法来处理异步任务的结果和异常。在处理函数中,首先判断异常是否为 null,如果不为 null,则表示任务发生了异常,我们可以在这里进行异常处理,并返回一个默认值。如果异常为 null,则表示任务执行正常,我们可以在这里对正常结果进行处理,并返回相应的字符串。

最后,使用 join() 方法等待异步任务完成,并获取最终的处理结果。

需要注意的是,handle() 方法返回的是一个新的 CompletableFuture 对象,它表示一个新的异步任务。这个新的任务会在原始任务完成后被触发,并执行处理函数。

通过使用 handle() 方法,我们可以灵活地处理异步任务的结果和异常,提供自定义的处理逻辑,从而实现更加复杂的业务需求。

2. CompletableFuture的并发限制

CompletableFuture 类本身并没有提供直接的并发限制功能。它是 Java 中用于处理异步编程的工具类,通过 CompletableFuture 可以方便地进行异步任务的组合、串行化、并行化等操作。

如果你需要对异步任务进行并发限制,可以借助 Executor 框架提供的线程池来实现。Executors 类提供了一些静态方法来创建不同类型的线程池,其中的线程池可以控制并发执行的任务数量。

下面是一个使用 Executors 创建固定大小线程池来限制 CompletableFuture 并发的示例:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConcurrentLimitExample {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(5);

        // 创建多个 CompletableFuture,并指定线程池
        CompletableFuture<Void> task1 = CompletableFuture.runAsync(() -> {
            // 异步任务逻辑
            System.out.println("Task 1");
        }, executor);

        CompletableFuture<Void> task2 = CompletableFuture.runAsync(() -> {
            // 异步任务逻辑
            System.out.println("Task 2");
        }, executor);

        CompletableFuture<Void> task3 = CompletableFuture.runAsync(() -> {
            // 异步任务逻辑
            System.out.println("Task 3");
        }, executor);

        // 等待所有 CompletableFuture 完成
        CompletableFuture.allOf(task1, task2, task3)
                .join();

        // 关闭线程池
        executor.shutdown();
    }
}

在上面的示例中,我们使用 Executors.newFixedThreadPool(5) 创建一个固定大小为 5 的线程池。然后,我们创建了多个 CompletableFuture 对象,并通过指定线程池来执行异步任务。通过这种方式,我们可以控制并发执行的任务数量,限制在线程池提供的线程数范围内。

需要注意的是,通过线程池控制并发执行的任务数量是有限度的,取决于线程池的配置和硬件资源。如果任务数超过了线程池的容量,超出部分的任务会进入等待队列,直到有空闲线程可用。

使用线程池来限制 CompletableFuture 的并发操作可以帮助控制资源的使用,防止资源过度消耗和线程过多导致的性能问题。

3. CompletableFuture与Stream的结合使用

CompletableFuture 和 Stream 是 Java 中两个强大且灵活的工具,它们可以很好地结合使用,以实现异步处理和流式操作的组合。下面是一些使用 CompletableFuture 和 Stream 结合的示例:

  1. 异步任务的并行处理:
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

public class CompletableFutureWithStream {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5);

        // 使用 CompletableFuture 来异步处理每个元素
        List<CompletableFuture<String>> futures = numbers.stream()
                .map(number -> CompletableFuture.supplyAsync(() -> process(number)))
                .collect(Collectors.toList());

        // 等待所有 CompletableFuture 完成,并获取结果列表
        List<String> results = futures.stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());

        // 打印结果
        results.forEach(System.out::println);
    }

    private static String process(Integer number) {
        // 模拟一个耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Processed: " + number;
    }
}

在上述示例中,我们使用 CompletableFuture 的 supplyAsync() 方法来创建异步任务,并通过 Stream 的 map() 方法将每个元素映射成对应的 CompletableFuture。然后,我们将这些 CompletableFuture 收集到一个列表中。

接着,我们通过 Stream 的 map() 方法将列表中的 CompletableFuture 转换为实际的结果,并将结果收集到另一个列表中。最后,我们打印出每个结果。

这样的方式可以实现异步任务的并行处理,提高处理效率。

  1. 异步任务的顺序处理:
import java.util.List;
import java.util.concurrent.CompletableFuture;

public class CompletableFutureWithStream {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5);

        CompletableFuture<Void> future = CompletableFuture.completedFuture(null); // 创建一个已完成的 CompletableFuture

        // 顺序处理每个元素的异步任务
        for (Integer number : numbers) {
            future = future.thenComposeAsync(result -> CompletableFuture.supplyAsync(() -> process(number)));
        }

        // 等待最后一个 CompletableFuture 完成
        future.join();
    }

    private static String process(Integer number) {
        // 模拟一个耗时操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Processed: " + number);
        return null;
    }
}

在上述示例中,我们使用一个已完成的 CompletableFuture 作为起点。然后,我们在循环中以顺序的方式将每个元素的异步任务连接起来,通过 thenComposeAsync() 方法将每个异步任务串联起来。

通过这样的方式,每个异步任务都会等待前一个任务完成后才会触发执行,从而实现了顺序处理的效果。

需要注意的是,在顺序处理的情况下,后续的异步任务会等待前一个任务完成后才会被触发,所以整体的执行时间会比并行处理长。这取决于异步任务的耗时和处理逻辑。

通过 CompletableFuture 和 Stream 的结合使用,可以快速实现复杂的异步处理和流式操作。你可以根据具体的需求和业务场景选择合适的方式来组合它们。

七、 示例和案例分析

1. 使用CompletableFuture实现并发下载

使用 CompletableFuture 来实现并发下载是一个很常见的场景,下面是一个使用 CompletableFuture 实现并发下载的示例:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConcurrentDownloader {
    public static void main(String[] args) {
        List<String> urls = Arrays.asList(
                "https://example.com/image1.jpg",
                "https://example.com/image2.jpg",
                "https://example.com/image3.jpg"
        );

        // 创建固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // 使用 CompletableFuture 并发下载图片
        List<CompletableFuture<Void>> downloadFutures = urls.stream()
                .map(url -> CompletableFuture.runAsync(() -> downloadImage(url), executor))
                .toList();

        // 等待所有 CompletableFuture 完成
        CompletableFuture.allOf(downloadFutures.toArray(new CompletableFuture[0]))
                .join();

        // 关闭线程池
        executor.shutdown();
    }

    private static void downloadImage(String url) {
        try (InputStream in = new URL(url).openStream();
             FileOutputStream out = new FileOutputStream(getFileName(url))) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesRead);
            }
            System.out.println("Downloaded: " + url);
        } catch (IOException e) {
            System.err.println("Failed to download image: " + url);
        }
    }

    private static String getFileName(String url) {
        String[] segments = url.split("/");
        return segments[segments.length - 1];
    }
}

在上述示例中,我们首先创建了一个包含要下载图片的 URL 列表。然后,我们创建了一个固定大小的线程池,通过 Executors.newFixedThreadPool(3) 创建了一个大小为 3 的线程池。

接下来,我们使用 CompletableFuture 和 Stream 的结合来并发下载图片。通过 CompletableFuture.runAsync() 方法将下载图片的逻辑包装成一个异步任务,并指定线程池执行该任务。然后,将所有的 CompletableFuture 收集到一个列表中。

最后,我们使用 CompletableFuture.allOf() 方法来等待所有的 CompletableFuture 完成,并通过 join() 方法阻塞当前线程,直到所有任务完成。

需要注意的是,并发下载图片涉及到网络 IO 操作,可能会耗费较长的时间。为了避免阻塞主线程,我们采用了异步的方式进行下载,并且使用了线程池来控制并发执行的任务数量。

通过 CompletableFuture 的使用,我们可以简洁地实现并发下载的功能,并可以灵活地控制并发度和线程池的大小。这样可以提高下载效率,并充分利用可用的资源。

2. 使用CompletableFuture优化订单处理流程

使用 CompletableFuture 可以优化订单处理流程,提高效率和并发性。下面是一个使用 CompletableFuture 优化订单处理流程的示例:

假设有一个订单处理系统,包含以下几个步骤:验证订单、扣除库存、生成发货单、发送通知。

import java.util.concurrent.CompletableFuture;

public class OrderProcessing {
    public static void main(String[] args) {
        CompletableFuture<Void> orderProcessingFuture = CompletableFuture.completedFuture(null);

        // 验证订单
        orderProcessingFuture = orderProcessingFuture.thenComposeAsync(result -> CompletableFuture.supplyAsync(() -> validateOrder()))
                .thenApplyAsync(result -> {
                    if (result) {
                        System.out.println("订单验证通过");
                    } else {
                        System.out.println("订单验证失败");
                    }
                    return result;
                });

        // 扣除库存
        orderProcessingFuture = orderProcessingFuture.thenComposeAsync(result -> CompletableFuture.supplyAsync(() -> deductInventory()))
                .thenAcceptAsync(result -> {
                    if (result) {
                        System.out.println("库存扣除成功");
                    } else {
                        System.out.println("库存扣除失败");
                    }
                });

        // 生成发货单
        orderProcessingFuture = orderProcessingFuture.thenComposeAsync(result -> CompletableFuture.supplyAsync(() -> generateInvoice()))
                .thenAcceptAsync(invoice -> {
                    System.out.println("发货单生成成功:" + invoice);
                });

        // 发送通知
        orderProcessingFuture = orderProcessingFuture.thenRunAsync(() -> sendNotification());

        // 等待订单处理完成
        orderProcessingFuture.join();
    }

    private static boolean validateOrder() {
        // 模拟订单验证操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }

    private static boolean deductInventory() {
        // 模拟扣除库存操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }

    private static String generateInvoice() {
        // 模拟生成发货单操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Invoice-123456";
    }

    private static void sendNotification() {
        // 模拟发送通知操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("通知已发送");
    }
}

在上述示例中,我们通过 CompletableFuture 来优化订单处理流程。每个步骤都被封装为一个异步任务,并使用 thenComposeAsync() 方法将前一个任务的结果传递给下一个任务。同时,通过 thenApplyAsync()thenAcceptAsync()thenRunAsync() 方法可以在任务完成后执行相应的操作。

在验证订单、扣除库存和生成发货单这些关键步骤中,我们使用 CompletableFuture.supplyAsync() 方法来创建异步任务。这些任务会异步地执行相关操作,并返回结果或者不返回。

最后,在订单处理完成后,我们使用 join() 方法阻塞主线程,等待所有任务完成。

通过使用 CompletableFuture,我们可以以一种非常直观和简洁的方式组织和优化订单处理流程。异步任务的执行不会阻塞主线程,可以提高并发性和系统的响应能力。

八、总结

1. 重点总结CompletableFuture的核心概念和用法

CompletableFuture 是 Java 8 引入的一个用于处理异步编程的工具类,它提供了丰富的方法来处理异步任务和操作结果。下面是 CompletableFuture 的核心概念和用法的总结:

  1. 异步任务的创建:使用 CompletableFuture.supplyAsync()CompletableFuture.runAsync()
    CompletableFuture.completedFuture() 方法来创建异步任务。supplyAsync()
    runAsync() 分别用于有返回值和无返回值的异步任务的创建。

  2. 异步任务的串行关系:通过 thenApply()thenAccept()thenRun() 方法将一个任务与另一个任务进行串联。thenApply() 用于对上一个任务的结果进行转换,thenAccept()
    用于消费上一个任务的结果,thenRun() 用于在上一个任务完成后执行一段代码。

  3. 异步任务的并行关系:通过 thenCombine()thenAcceptBoth()runAfterBoth() 等方法将多个任务进行组合。这些方法用于在多个任务之间建立依赖关系,并在它们都完成后进行相应的操作。

  4. 异步任务的异常处理:使用 exceptionally() 方法或 handle() 方法来处理异步任务中的异常。exceptionally() 方法用于处理异常,并返回一个默认值,handle()
    方法可以处理异常,并提供一个替代的计算结果。

  5. 异步任务的等待和合并:使用 join() 方法等待一个任务的完成,并获取其结果。使用 allOf() 方法等待多个任务的完成,或者使用 anyOf() 方法等待任意一个任务的完成。

  6. 异步任务的组合和并发控制:使用 thenCompose()thenCombine()allOf() 等方法对多个任务进行组合和并发控制。thenCompose()
    用于将前一个任务的结果作为下一个任务的输入,thenCombine() 用于将两个任务的结果进行合并,allOf()
    用于等待多个任务的完成。

  7. 线程池的使用:可以通过 CompletableFuture.runAsync()CompletableFuture.supplyAsync()
    CompletableFuture.thenRunAsync() 等方法指定自定义的线程池来执行异步任务。

CompletableFuture 提供了一种方便而强大的方式来处理异步编程,可以避免显式地使用线程或回调函数来处理异步操作。它具有丰富的方法和组合功能,可以实现复杂的异步流程控制和并发控制。通过合理地应用 CompletableFuture,我们可以提高程序的性能和可读性。

2. 强调CompletableFuture在多线程编程中的价值和应用场景

CompletableFuture 在多线程编程中具有重要的价值和广泛的应用场景。下面是强调 CompletableFuture 在多线程编程中的价值和常见的应用场景:

  1. 并行任务执行:CompletableFuture 可以方便地启动多个异步任务,并发地执行它们,从而提高系统的并发性和吞吐量。通过将任务串联或组合起来,可以构建复杂的任务流水线和并发控制逻辑。

  2. 异步操作与非阻塞调用:CompletableFuture 支持异步任务的创建和执行,可以在异步任务执行过程中继续执行其他操作,而不需要显式地创建和管理线程。这样可以避免阻塞主线程,提高系统的响应能力和资源利用率。

  3. 响应式编程:CompletableFuture 的链式操作和回调函数机制使其非常适合用于实现响应式编程模型。我们可以通过 thenApply、thenAccept、thenRun 等方法定义异步任务的处理逻辑,并在任务完成后自动触发回调函数进行后续的操作。

  4. 异常处理和容错机制:CompletableFuture 提供了丰富的异常处理方法,例如 exceptionally、handle 等,可以方便地处理异步任务中出现的异常,并提供默认值或备用计算结果。这样可以增强程序的健壮性和容错性。

  5. 并发控制和任务合并:CompletableFuture 提供了多种方法来控制异步任务的并发度,例如 thenCompose、thenCombine、allOf 等。通过合理地组合和并发控制,可以实现更高效的任务调度和资源管理。

  6. 自定义线程池:CompletableFuture 允许我们通过指定自定义的 Executor 来执行异步任务,从而可以灵活地控制线程池的大小、线程池的属性等,以满足应用程序的需求。

  7. 异步 I/O 操作:CompletableFuture 还可以与异步 I/O 操作结合使用,例如与 NIO 的 AsynchronousFileChannel、AsynchronousSocketChannel 等配合,实现高效的异步文件读写和网络通信。

CompletableFuture 在多线程编程中具有强大的功能和灵活的应用场景。它简化了异步编程的复杂性,提高了代码的可读性和可维护性,同时也提升了系统的性能和并发性。无论是对于高并发的服务器端应用,还是对于异步处理的客户端应用,CompletableFuture 都是一个强大的工具。

;