CompletableFuture使用详解

本文详细介绍了Java 8中CompletableFuture的功能及使用方法,包括异步计算、Future接口的局限性、CompletionStage概念及其与CompletableFuture的关系。并通过多个示例展示了如何利用CompletableFuture进行异步编程,提高程序效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

 

异步计算

Future 接口的局限性

CompletionStage

CompletableFuture

下面是CompletableFuture的一些常用的方法:


异步计算

  • 所谓异步调用其实就是实现一个可无需等待被调用函数的返回值而让操作继续运行的方法。在 Java 语言中,简单的讲就是另启一个线程来完成调用中的部分计算,使调用继续运行或返回,而不需要等待计算结果。但调用者仍需要取线程的计算结果。

  • JDK5新增了Future接口,用于描述一个异步计算的结果。虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的 CPU 资源,而且也不能及时地得到计算结果。

  • 以前我们获取一个异步任务的结果可能是这样写的

Future 接口的局限性

Future接口可以构建异步应用,但依然有其局限性。它很难直接表述多个Future 结果之间的依赖性。实际开发中,我们经常需要达成以下目的:

  1. 将多个异步计算的结果合并成一个

  2. 等待Future集合中的所有任务都完成

  3. Future完成事件(即,任务完成以后触发执行动作)

  4. 。。。

 

CompletionStage

  • CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段

  • 一个阶段的计算执行可以是一个Function,Consumer或者Runnable。比如:stage.thenApply(x -> square(x)).thenAccept(x -> System.out.print(x)).thenRun(() -> System.out.println())

  • 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发

CompletableFuture

  • 在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法。
  • 它可能代表一个明确完成的Future,也有可能代表一个完成阶段( CompletionStage ),它支持在计算完成以后触发一些函数或执行某些动作。
  • 它实现了Future和CompletionStage接口

 

 在JAVA中你可以理解为可以使用它来创建一个异步线程处理你想要处理的内容,并且这个异步线程是非阻塞的。同时你要注意的是,每一个任务都可以是一个Feature 或者是 CompletionStage 都是后台守护线程(Daemon),也就是说,随着主线程的死亡,守护线程不管有没有完成任务,也会结束。

下面是CompletableFuture的一些常用的方法:

public class WelfareImageController {

    /**
     * runAsync 无返回值
     *
     * @throws Exception
     */
    public static void runAsync() throws Exception {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
            }
            System.out.println("run end ...");
        });

        future.get();
    }

    /**
     * supplyAsync
     * 有返回值
     *
     * @throws Exception
     */
    public static void supplyAsync() throws Exception {
        Instant start = Instant.now();
        CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
            }
            System.out.println("run end ...");
            return System.currentTimeMillis();
        });

        long time = future.get();
        System.out.println("time = " + time);
        Instant end = Instant.now();
        System.out.println("总耗时:" + Duration.between(start, end).getSeconds());

    }


    /**
     * whenComplete 方法
     * 异步线程任务执行完后调用 whenComplete 方法
     *
     * @throws Exception
     */
    public static void whenComplete() throws Exception {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
            }
            if (new Random().nextInt() % 2 >= 0) {
                int i = 12 / 0;
            }
            System.out.println("run end ...");
        });

        future.whenComplete(new BiConsumer<Void, Throwable>() {
            @Override
            public void accept(Void t, Throwable action) {
                System.out.println("执行完成!");
            }

        });

        // runAsync 执行异常的时候就执行此方法
        future.exceptionally(new Function<Throwable, Void>() {
            @Override
            public Void apply(Throwable t) {
                System.out.println("执行失败!" + t.getMessage());
                return null;
            }
        });

        TimeUnit.SECONDS.sleep(2);
    }


    /**
     * thenApply 方法
     * 异步线程执行完返回的结果作为下个任务的参数
     * 注意: 任务出现异常则不执行 thenApply 方法
     *
     * @throws Exception
     */
    private static void thenApplyOld() throws Exception {
        CompletableFuture<Long> future = CompletableFuture.supplyAsync(new Supplier<Long>() {
            @Override
            public Long get() {
                long result = new Random().nextInt(100);
                System.out.println("result1=" + result);
                return result;
            }
        }).thenApply(new Function<Long, Long>() {
            @Override
            public Long apply(Long t) {
                long result = t * 5;
                System.out.println("result2=" + result);
                return result;
            }
        });

        long result = future.get();
        System.out.println(result);
    }

    /**
     * thenApply 方法
     * 上一个方法的简化
     *
     * @throws Exception
     */
    private static void thenApply() throws Exception {
        CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {
            long result = new Random().nextInt(100);
            System.out.println("result1=" + result);
            return result;
        }).thenApply(t -> {
            long result = t * 5;
            System.out.println("result2=" + result);
            return result;
        });

        long result = future.get();
        System.out.println(result);
    }

    /**
     * handle 方法
     * handle 方法和 thenApply 方法处理方式基本一样。
     * 不同的是 handle 是在任务完成后再执行,还可以处理异常的任务。
     * thenApply 只可以执行正常的任务,任务出现异常则不执行 thenApply 方法。
     *
     * @throws Exception
     */
    public static void handle() throws Exception {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {

            @Override
            public Integer get() {
                int i = 10 / 0;
                return new Random().nextInt(10);
            }
        }).handle(new BiFunction<Integer, Throwable, Integer>() {
            @Override
            public Integer apply(Integer param, Throwable throwable) {
                int result = -1;
                if (throwable == null) {
                    result = param * 2;
                } else {
                    System.out.println(throwable.getMessage());
                }
                return result;
            }
        });
        System.out.println(future.get());
    }

    /**
     * thenAccept
     * 接收异步任务的返回结果进行处理 无返回值
     *
     * @param
     * @throws Exception
     */
    public static void thenAccept() throws Exception {
        CompletableFuture<Void> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                return new Random().nextInt(10);
            }
        }).thenAccept(integer -> {
            System.out.println(integer);
        });
        future.get();
    }

    /**
     * thenRun
     * 与上面的thenAccept 不同的是 不关心任务的处理结果 只要任务处理完就执行此方法
     * 注意: 任务遇到未抛出的异常则不会执行
     *
     * @param
     * @throws Exception
     */
    public static void thenRun() throws Exception {
        CompletableFuture<Void> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                return new Random().nextInt(10);
            }
        }).thenRun(() -> {
            System.out.println("thenRun ...");
        });
        future.get();
    }

    /**
     * thenCombine 合并任务
     * thenCombine 会把 两个 CompletionStage 的任务都执行完成后,把两个任务的结果一块交给 thenCombine 来处理。
     * 这里有点像stream流的reduce
     *
     * @param
     * @throws Exception
     */
    private static void thenCombine() throws Exception {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                return "hello";
            }
        });
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                return "hello";
            }
        });
        CompletableFuture<String> result = future1.thenCombine(future2, new BiFunction<String, String, String>() {
            @Override
            public String apply(String t, String u) {
                return t + " " + u;
            }
        });
        System.out.println(result.get());
    }

    /**
     * reduce 并行计算:
     * 解释一下: 后一个 Integer::sum 就是对前面 一个Integer::sum 阶段性累加结果进行合并
     *
     * @param
     * @throws Exception
     */
    public static void reduceTest() {
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        Integer reduce = integers.parallelStream().reduce(0, Integer::sum, Integer::sum);
        System.out.println(reduce);
    }

    /**
     * thenAcceptBoth
     * 当两个CompletionStage都执行完成后,把结果一块交给thenAcceptBoth来进行消耗
     *
     * @param
     * @throws Exception
     */
    private static void thenAcceptBoth() throws Exception {
        CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                System.out.println("f1=" + t);
                return t;
            }
        });

        CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                System.out.println("f2=" + t);
                return t;
            }
        });
        f1.thenAcceptBoth(f2, new BiConsumer<Integer, Integer>() {
            @Override
            public void accept(Integer t, Integer u) {
                System.out.println("f1=" + t + ";f2=" + u + ";");
            }
        });
    }

    private static void thenAcceptBothNew() throws Exception {
        CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                try {
                    TimeUnit.SECONDS.sleep(t);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("f1="+t);
                return t;
            }
        });

        CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                try {
                    TimeUnit.SECONDS.sleep(t);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("f2="+t);
                return t;
            }
        });
        f1.thenAcceptBoth(f2, new BiConsumer<Integer, Integer>() {
            @Override
            public void accept(Integer t, Integer u) {
                System.out.println("f1="+t+";f2="+u+";");
            }
        });
    }

    /**
     * applyToEither 方法
     * 两个CompletionStage,谁执行返回的结果快,我就用那个CompletionStage的结果进行下一步的转化操作。
     * 慢的那个任务也会执行 但不会被applyToEither调用返回值做任何处理
     *
     * @param
     * @throws Exception
     */
    private static void applyToEither() throws Exception {
        CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                try {
                    TimeUnit.SECONDS.sleep(t);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("f1=" + t);
                return t;
            }
        });
        CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                try {
                    TimeUnit.SECONDS.sleep(t);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("f2=" + t);
                return t;
            }
        });

        CompletableFuture<Integer> result = f1.applyToEither(f2, new Function<Integer, Integer>() {
            @Override
            public Integer apply(Integer t) {
                System.out.println(t);
                return t * 2;
            }
        });

        System.out.println(result.get());
    }

    /**
     * acceptEither 方法
     * 两个CompletionStage,谁执行返回的结果快,我就用那个CompletionStage的结果进行下一步的消耗操作。
     *
     * @param
     * @throws Exception
     */
    private static void acceptEither() throws Exception {
        CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                System.out.println("f1=" + t);
                return t;
            }
        });

        CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                System.out.println("f2=" + t);
                return t;
            }
        });
        f1.acceptEither(f2, new Consumer<Integer>() {
            @Override
            public void accept(Integer t) {
                System.out.println(t);
            }
        });
    }
    private static void acceptEitherNew() throws Exception {
        CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                try {
                    TimeUnit.SECONDS.sleep(t);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("f1="+t);
                return t;
            }
        });

        CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                try {
                    TimeUnit.SECONDS.sleep(t);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("f2="+t);
                return t;
            }
        });
        f1.acceptEither(f2, new Consumer<Integer>() {
            @Override
            public void accept(Integer t) {
                System.out.println(t);
            }
        });
        f1.get();
        f2.get();
    }

    /**
     * runAfterEither 方法
     * 两个CompletionStage,任何一个完成了都会执行下一步的操作(Runnable)
     *
     * @param
     * @throws Exception
     */
    private static void runAfterEither() throws Exception {
        CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                System.out.println("f1=" + t);
                return t;
            }
        });

        CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                System.out.println("f2=" + t);
                return t;
            }
        });
        f1.runAfterEither(f2, new Runnable() {
            @Override
            public void run() {
                System.out.println("上面有一个已经完成了。");
            }
        });
        System.out.println("f1:" + f1.get());
        System.out.println("f2:" + f2.get());
    }

    /**
     * runAfterBoth
     * 两个CompletionStage,都完成了计算才会执行下一步的操作(Runnable)
     *
     * @param
     * @throws Exception
     */
    private static void runAfterBoth() throws Exception {
        CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                System.out.println("f1=" + t);
                return t;
            }
        });

        CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                System.out.println("f2=" + t);
                return t;
            }
        });
        f1.runAfterBoth(f2, new Runnable() {

            @Override
            public void run() {
                System.out.println("上面两个任务都执行完成了。");
            }
        });
    }

    /**
     * thenCompose 方法
     * thenCompose 方法允许你对两个 CompletionStage 进行流水线操作,第一个操作完成时,将其结果作为参数传递给第二个操作。
     *
     * @param
     * @throws Exception
     */
    private static void thenCompose() throws Exception {
        CompletableFuture<Integer> f = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int t = new Random().nextInt(3);
                System.out.println("t1=" + t);
                return t;
            }
        }).thenCompose(new Function<Integer, CompletionStage<Integer>>() {
            @Override
            public CompletionStage<Integer> apply(Integer param) {
                return CompletableFuture.supplyAsync(new Supplier<Integer>() {
                    @Override
                    public Integer get() {
                        int t = param * 2;
                        System.out.println("t2=" + t);
                        return t;
                    }
                });
            }

        });
        System.out.println("thenCompose result : " + f.get());
    }

    /**
     * allOf() 方法
     *
     * @param
     * @throws Exception
     */
    static void futureTest() {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("future1 finished!");
            return "future1 finished!";
        });
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("future2 finished!");
            return "future2 finished!";
        });
        //阻塞,直到所有任务结束。
        System.out.println(System.currentTimeMillis() + ":阻塞");
        CompletableFuture.allOf(future1, future2).join();
        System.out.println(System.currentTimeMillis() + ":阻塞结束");
        System.out.println("future1: " + future1.isDone() + " future2: " + future2.isDone());
    }

    static String queryCode(String name) {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
        return "601857";
    }

    static Double fetchPrice(String code) {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
        return 5 + Math.random() * 20;
    }


    public static void main(String[] args) throws Exception {
//        Thread.sleep(4000);
//        WelfareImageController.thenAcceptBothNew();

        // 第一个任务:
        CompletableFuture<String> cfQuery = CompletableFuture.supplyAsync(() -> {
            return queryCode("中国石油");
        });
        // cfQuery成功后继续执行下一个任务:
        CompletableFuture<Double> cfFetch = cfQuery.thenApplyAsync((code) -> {
            return fetchPrice(code);
        });
        // cfFetch成功后打印结果:
        cfFetch.thenAccept((result) -> {
            System.out.println("price: " + result);
        });
        // 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
        Thread.sleep(1000);
    }
}

参考:https://www.jianshu.com/p/6bac52527ca4

https://www.jianshu.com/p/1db996cf7574

https://www.cnblogs.com/cjsblog/p/9267163.html

https://blog.youkuaiyun.com/finalheart/article/details/87615546

<think>嗯,用户想了解CompletableFuture的原理和详细实现,特别是Java异步编程方面的深入分析。首先,我需要回顾一下之前提供的引用内容,看看有哪些信息可以利用。引用[1]提到CompletableFuture是Java 8引入的,用于异步编程,属于java.util.concurrent包,相比传统Future更灵活,支持链式调用、任务合并和异常处理。引用[2]强调了它的高效异步模式和并发编程的简洁性。引用[3]指出CompletableFuture有很多方法和内部类,需要分步分析。引用[4]提到了Java并发的发展,包括ForkJoin框架等。 用户的问题是关于原理和实现的详细解析,所以需要深入讨论内部机制,比如如何实现异步任务的管理、回调链的处理、线程池的使用,以及如何组合多个任务。可能还需要涉及CompletableFuture的状态机、依赖关系处理、异常传播机制等。 首先,CompletableFuture基于ForkJoinPool,这点应该在实现原理部分提到。然后,它的核心结构可能涉及到每个阶段(stage)作为一个节点,形成链表或栈结构来处理依赖。状态管理方面,可能用volatile变量来保存结果和状态变化,确保可见性。 另外,需要解释CompletableFuture如何通过thenApply、thenAccept等方法链式组合任务,这些方法如何注册回调,并在前一个阶段完成时触发后续操作。这里可能涉及CompletionStage接口的实现,以及各种不同类型的Completion子类,比如UniApply、BiApply等,处理不同的依赖情况。 异常处理也是一个重点,CompletableFuture如何捕获异常并传递给后续的exceptionally或handle方法。需要说明当某个阶段出现异常时,后续依赖的阶段如何被触发,并处理异常结果。 线程池的集成部分,用户可能想知道默认使用的线程池,以及如何自定义。比如,默认使用ForkJoinPool.commonPool(),但也可以指定自定义的Executor。在实现中,如何将任务提交到线程池,并在任务完成后执行回调。 还有,组合多个CompletableFuture的方法,如allOf、anyOf,这些是如何实现的,可能涉及内部计数器或条件判断,等待所有或任意一个Future完成。 另外,性能优化方面,CompletableFuture如何减少线程阻塞,通过非阻塞的回调机制提高效率。对比传统的Future.get(),CompletableFuture通过回调通知机制避免了阻塞等待。 可能用户还想了解与响应式编程的区别,或者与其他异步框架如RxJava的不同之处,但根据问题,重点应放在CompletableFuture本身的原理和实现细节上。 需要确保回答结构清晰,分点讲解核心原理、状态管理、依赖处理、线程池机制、组合任务和异常处理等部分。同时,结合代码示例说明,比如创建异步任务的方法,链式调用的例子,以及异常处理的示例。 最后,生成相关问题时要围绕用户可能的进一步疑问,比如与传统的Future对比、如何选择线程池、实际应用场景、性能优化方法、异常处理最佳实践,以及和响应式编程的结合等。</think># CompletableFuture原理与实现深度解析 ## 一、核心实现原理 CompletableFuture基于观察者模式构建异步任务链,内部采用$stage$链表结构管理任务依赖关系。每个阶段通过$UniCompletion$/$BiCompletion$等内部类实现回调注册,采用无锁CAS操作保证线程安全[^3]。 状态机实现包含三个关键变量: $$ \begin{cases} result & \text{存储计算结果/异常对象} \\ stack & \text{等待触发的回调函数栈} \\ nest & \text{防止递归调用计数器} \end{cases} $$ ## 二、任务执行流程 1. **异步启动**:通过`supplyAsync()`创建任务时,默认使用$ForkJoinPool.commonPool()$[^4] ```java CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> { // 异步任务逻辑 return "result"; }); ``` 2. **依赖传播**:每个`thenApply()`调用生成新的$UniApply$节点并压入前驱的栈顶 3. **完成触发**:当前任务完成后,通过`postComplete()`方法反向遍历栈节点触发后续操作 ## 三、线程池集成机制 | 配置方式 | 实现类 | 适用场景 | |-------------------|-------------------------|-----------------------| | 默认线程池 | ForkJoinPool.commonPool | CPU密集型任务 | | 自定义线程池 | ThreadPoolExecutor | IO密集型任务 | | 直接执行 | Runnable::run | 快速回调(同线程执行) | ## 四、组合任务实现 1. **AND聚合**:`allOf()`通过$BiRelay$合并多个future结果 2. **OR聚合**:`anyOf()`使用$AtomicReference$竞争设置首个完成结果 3. **嵌套组合**:`thenCompose()`通过$UniCompose$实现future级联 ## 五、异常处理机制 1. 异常传播路径:通过$AltResult$包装异常对象沿任务链传递 2. 恢复机制:`exceptionally()`注册的恢复函数可返回替代值 3. 全局捕获:`handle()`方法同时接收结果和异常参数 ```java CompletableFuture.supplyAsync(() -> { if (new Random().nextBoolean()) throw new RuntimeException("模拟异常"); return "data"; }).exceptionally(ex -> { System.out.println("捕获异常: " + ex.getMessage()); return "default"; }); ``` ## 六、性能优化设计 1. 延迟提交:使用$LazyExecutor$减少线程切换开销 2. 栈优化:采用Treiber栈结构提升并发压栈性能[^2] 3. 对象复用:内部$ASYNC$/$NIL$等标记对象减少内存分配
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值