线程系列 3 - 关于 CompletableFuture

news2024/11/25 23:30:32

线程系列3-关于 CompletableFuture

  • 1、从 Future 接口说起
  • 2、CompletableFuture 对 Future 的改进
    • 2.1、CompletionStage 接口类
    • 2.2、runAsync 和 supplyAsync 创建子任务
    • 2.3、 whenComplete 和 exceptionally 异步任务回调钩子
    • 2.4、调用 handle() 方法统一处理异常和结果
    • 2.5、异步任务的串行执行
      • 2.5.1、thenApply() 方法
      • 2.5.2、thenRun() 方法
      • 2.5.3、thenAccept() 方法
      • 2.5.4、thenCompose() 方法
    • 2.6、异步任务的合并执行
      • 2.6.1、thenCombine() 方法
      • 2.6.2、runAfterBoth() 方法
      • 2.6.3、thenAcceptBoth() 方法
      • 2.6.4、allOf() 方法等待所有的任务结束
    • 2.7、异步任务的选择执行
      • 2.7.1、applyToEither() 方法
      • 2.7.2、runAfterEither() 方法
      • 2.7.3、acceptEither() 方法
    • 2.8、CompletableFuture 的基础方法

1、从 Future 接口说起

 

        Future 接口(常用实现类:FutureTask)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。Future 接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力的复杂业务。其有三个特点:多线程、有返回、异步任务,总结就是异步多线程任务执行且返回有结果。

所有已知实现类

CompletableFuture, CountedCompleter, ForkJoinTask, FutureTask, RecursiveAction, RecursiveTask, SwingWorker

接口方法:

方法返回值描述
cancel(boolean mayInterruptIfRunning)boolean试图取消此任务的执行
get()V如果计算完成则获取其结果,若计算未完成则阻塞直到获取其结果
get(long timeout, TimeUnit unit)V等待固定时长,如果在这个时长内程序还是没有运行完成,就会出现 juc.TimeOutException 异常。如果完成则返回其结果
isCancelled()boolean如果这个任务完成之前取消则返回 true
isDone()boolean如果完成这个任务则返回true

 
FutureTask类图:
 
在这里插入图片描述
 
代码示例 1-1 :

import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;

@Slf4j
public class FutureDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(4);
        FutureTask<String> futureTask1 = new FutureTask<String>(() -> {
            log.info("hello ,this is futureTask1 call().");
            TimeUnit.SECONDS.sleep(3);
            return "futureTask1 call result";
        });
        pool.submit(futureTask1);
        // 这里会阻塞
        String futureTaskResult1= futureTask1.get();
        log.info("print the futureTaskResult1 = {}", futureTaskResult1);

        FutureTask<String> futureTask2 = new FutureTask<String>(() -> {
            log.info("hello ,this is futureTask2 call().");
            // 这里睡眠 10 秒
            TimeUnit.SECONDS.sleep(10);
            return "futureTask2 call result";
        });
        pool.submit(futureTask2);
        String futureTaskResult2 = null;
        try {
            // 这里只等待两秒,如果两秒后futureTask2还没执行出结果,那就抛出异常
            futureTaskResult2= futureTask2.get(2, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            log.error("futureTaskResult2 获取失败,设置的2秒等待超时,过期不等候");
            e.printStackTrace();
        }
        log.info("print the futureTaskResult2 = {}", futureTaskResult1);

        FutureTask<String> futureTask3 = new FutureTask<String>(() -> {
            log.info("hello ,this is futureTask3 call().");
            // 这里睡眠 10 秒
            TimeUnit.SECONDS.sleep(10);
            return "futureTask3 call result";
        });
        pool.submit(futureTask3);
        while (true) {
            // 使用 isDone() 方法轮询的模式,巨耗费CPU
            if (futureTask3.isDone()) {
                String futureTaskResult3 = futureTask3.get();
                log.info("print the futureTaskResult3 = {}", futureTaskResult3);
                break;
            } else {
                try {
                    // 每隔1秒 进行轮询
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.error("futureTask3 处理中,不要催,越催越慢");
            }
        }

        log.info("主线程的逻辑结束");
        pool.shutdown();
    }
}

打印结果 1-1 :

22:42:16.626 [pool-1-thread-1] INFO com.FutureDemo - hello ,this is futureTask1 call().
22:42:19.645 [main] INFO com.FutureDemo - print the futureTaskResult1 = futureTask1 call result
22:42:19.651 [pool-1-thread-2] INFO com.FutureDemo - hello ,this is futureTask2 call().
22:42:21.660 [main] ERROR com.FutureDemo - futureTaskResult2 获取失败,设置的2秒等待超时,过期不等候
22:42:21.662 [main] INFO com.FutureDemo - print the futureTaskResult2 = futureTask1 call result
22:42:21.664 [pool-1-thread-3] INFO com.FutureDemo - hello ,this is futureTask3 call().
java.util.concurrent.TimeoutException
	at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:204)
	at com.demo.future.FutureDemo.main(FutureDemo.java:30)
22:42:22.676 [main] ERROR com.FutureDemo - futureTask3 正在处理中,不要催,越催越慢
22:42:23.677 [main] ERROR com.FutureDemo - futureTask3 正在处理中,不要催,越催越慢
22:42:24.679 [main] ERROR com.FutureDemo - futureTask3 正在处理中,不要催,越催越慢
22:42:25.690 [main] ERROR com.FutureDemo - futureTask3 正在处理中,不要催,越催越慢
22:42:26.705 [main] ERROR com.FutureDemo - futureTask3 正在处理中,不要催,越催越慢
22:42:27.712 [main] ERROR com.FutureDemo - futureTask3 正在处理中,不要催,越催越慢
22:42:28.722 [main] ERROR com.FutureDemo - futureTask3 正在处理中,不要催,越催越慢
22:42:29.736 [main] ERROR com.FutureDemo - futureTask3 正在处理中,不要催,越催越慢
22:42:30.744 [main] ERROR com.FutureDemo - futureTask3 正在处理中,不要催,越催越慢
22:42:31.760 [main] ERROR com.FutureDemo - futureTask3 正在处理中,不要催,越催越慢
22:42:31.761 [main] INFO com.FutureDemo - print the futureTaskResult3 = futureTask3 call result
22:42:31.761 [main] INFO com.FutureDemo - 主线程的逻辑结束

Process finished with exit code 0

 

2、CompletableFuture 对 Future 的改进

 
        上面 FutureTask 处理任务后,获取结果时有些弊端也不够优雅,而且其提供的API对复杂场景,无法提供很好的支持,所以引出了 CompletableFuture,以声明式的方式优雅的处理各种复杂的需求。

        CompletableFuture 是JDK1.8 设计出来的,其提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。CompletableFuture 是 Future 的功能增强版,减少阻塞和轮询,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。

 

CompletableFuture 优点:

  • 异步任务结束时,会自动回调某个对象的方法。
  • 主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行。
  • 异步任务出错时,会自动回调某个对象的方法。

CompletableFuture 类图:
 
在这里插入图片描述
 

2.1、CompletionStage 接口类

 
        CompletableFuture 的功能之所以强大,很大一方面原因是实现了CompletionStage 接口,所以我们需要了解CompletionStage 接口类相关的一些方法。CompletionStage 代表某个同步或者异步计算的一个阶段,或者一系列异步任务中的一个子任务(或者阶段性任务)。每个CompletionStage 子任务所包装的可以是一个 Function、Consumer 或者 Runnable 函数式接口实例。
 
用表格的形式对比说明,这三个常用的函数式接口的特点:

函数式接口入参出参
Function
Consumer
Runnable

 

2.2、runAsync 和 supplyAsync 创建子任务

 
java.util.concurrent.CompletableFuture<T> 中的 runAsync 方法和 supplyAsync 方法。

    // 子任务包装一个Runnable实例,并调用ForkJoinPool.commonPool()线程池来执行
	// 创建的子任务无返回值CompletableFuture<Void>,默认线程值为ForkJoinPool.commonPool()
    public static CompletableFuture<Void> runAsync(Runnable runnable){
	     return asyncRunStage(ASYNC_POOL, runnable);
	};

    // 子任务包装一个Runnable实例,并调用指定的executor线程池来执行
    public static CompletableFuture<Void> runAsync(Runnable runnable,
	                                              Executor executor){
		 return asyncRunStage(screenExecutor(executor), runnable);		  
	};
	
	// 子任务包装一个Supplier实例,并调用ForkJoinPool.commonPool()线程池来执行
	// 创建的子任务有返回值CompletableFuture<U>,默认线程值为ForkJoinPool.commonPool()
	public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
        return asyncSupplyStage(ASYNC_POOL, supplier);
    }
	
	// 子任务包装一个Supplier实例,并使用指定的executor线程池来执行
	public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,
                                                       Executor executor) {
        return asyncSupplyStage(screenExecutor(executor), supplier);
    }

 
代码示例 2-2-1 :

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

/**
 * @author pzf
 * @ClassName CompletableFutureDemo
 * @description: TODO
 * @date 2023-07-16
 */
@Slf4j
public class CompletableFutureDemo {

    public static void main(String[] args) {
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
            log.info("{} -> 执行future1", Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        ExecutorService pool = Executors.newFixedThreadPool(3);
        CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
            log.info("{} -> 执行future2", Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, pool);
        try {
            log.info("future2.get() -> {}", future2.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
            log.info("{} -> 执行future3", Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "this future3 result";
        });
        try {
            log.info("future3.get() -> {}", future3.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        CompletableFuture<String> future4 = CompletableFuture.supplyAsync(() -> {
            log.info("{} -> 执行future4", Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "this future4 result";
        }, pool);
        try {
            log.info("future4.get() -> {}", future4.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        pool.shutdown();
    }

}

打印结果 2-2-1 :

21:07:20.411 [ForkJoinPool.commonPool-worker-3] INFO com.CompletableFutureDemo - ForkJoinPool.commonPool-worker-3 -> 执行future1
21:07:20.411 [pool-1-thread-1] INFO com.CompletableFutureDemo - pool-1-thread-1 -> 执行future2
21:07:22.424 [main] INFO com.CompletableFutureDemo - future2.get() -> null
21:07:22.426 [ForkJoinPool.commonPool-worker-3] INFO com.CompletableFutureDemo - ForkJoinPool.commonPool-worker-3 -> 执行future3
21:07:24.437 [main] INFO com.CompletableFutureDemo - future3.get() -> this future3 result
21:07:24.438 [pool-1-thread-2] INFO com.CompletableFutureDemo - pool-1-thread-2 -> 执行future4
21:07:26.447 [main] INFO com.CompletableFutureDemo - future4.get() -> this future4 result

 

2.3、 whenComplete 和 exceptionally 异步任务回调钩子

 
       当异步任务计算结果完成或者抛出异常的时候,可执行 whenComplete 和 exceptionally 等这些特定的回调钩子,避免了使用 get() 方法可能出现阻塞的情况。

java.util.concurrent.CompletableFuture<T> 中的回调钩子方法

	// 设置异步任务完成时的回调钩子
	public CompletableFuture<T> whenComplete(
        BiConsumer<? super T, ? super Throwable> action) {
        return uniWhenCompleteStage(null, action);
    }

    // 设置异步任务完成时的回调钩子,可能不在同一线程执行
    public CompletableFuture<T> whenCompleteAsync(
        BiConsumer<? super T, ? super Throwable> action) {
        return uniWhenCompleteStage(defaultExecutor(), action);
    }

    // 设置异步任务完成时的回调钩子,提交给线程池executor执行
    public CompletableFuture<T> whenCompleteAsync(
        BiConsumer<? super T, ? super Throwable> action, Executor executor) {
        return uniWhenCompleteStage(screenExecutor(executor), action);
    }
	
	// 设置异常处理的回调钩子
	public CompletableFuture<T> exceptionally(
                         Function<Throwable, ? extends T> fn) {
        return uniExceptionallyStage(fn);
    }

 
需要注意的点:

调用 cancel()方法取消 CompletableFuture时,任务被视为异常完成,completeExceptionally()方法所设置的异常回调钩子也会被执行。

如果没有设置异常回调钩子,发生内部异常时会有两种情况发生:

(1)在调用get()和get(long,TimeUnit)方法启动任务时,如果遇到内部异常,get()方法就会抛出ExecutionException(执行异常)。

(2)在调用join()和getNow(T)启动任务时(大多数情况下都是如此),如果遇到内部异常,join()和getNow(T)方法就会抛出CompletionException。

 
代码示例 2-3-1 :

import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

@Slf4j
public class WhenCompleteDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            log.info("{} --> future 第一阶段 coming", Thread.currentThread().getName());
            // 生成随机数
            int result = ThreadLocalRandom.current().nextInt(10);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("{} --> future 第一阶段 result = {}",
                    Thread.currentThread().getName(), result);
            return result;
            // 子任务完成时的回调钩子, v是第一阶段的结果result, e是第一阶段的异常
        }).whenComplete((v, e) -> {
            // 第一阶段没有异常
            if (e == null) {
                log.info("{} --> 进入回调钩子, 其中第一阶段的结果v = {}",
                        Thread.currentThread().getName(), v);
            }
            // exceptionally -> error 回调  不管是第一阶段还是回调钩子,出现异常都会触发该回调 
        }).exceptionally(e -> {
           log.error("{} --> 异常情况 --> {}", Thread.currentThread().getName(), e);
           return null;
        });
        log.info("{} --> 主线程执行逻辑", Thread.currentThread().getName());
        Integer result = future.get();
        log.info("{} --> result = {}", Thread.currentThread().getName(), result);
    }
    
}

打印结果 2-3-1 :

21:29:33.380 [ForkJoinPool.commonPool-worker-3] INFO com.WhenCompleteDemo - ForkJoinPool.commonPool-worker-3 --> future 第一阶段 coming
21:29:33.380 [main] INFO com.WhenCompleteDemo - main --> 主线程执行逻辑
21:29:35.399 [ForkJoinPool.commonPool-worker-3] INFO com.WhenCompleteDemo - ForkJoinPool.commonPool-worker-3 --> future 第一阶段 result = 5
21:29:35.399 [ForkJoinPool.commonPool-worker-3] INFO com.WhenCompleteDemo - ForkJoinPool.commonPool-worker-3 --> 进入回调钩子, 其中第一阶段的结果v = 5
21:29:35.399 [main] INFO com.WhenCompleteDemo - main --> result = 5

代码示例 2-3-2 :

import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;

@Slf4j
public class WhenCompleteDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            log.info("{} --> future 第一阶段 coming", Thread.currentThread().getName());
            // 生成随机数
            int result = ThreadLocalRandom.current().nextInt(10);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Integer.valueOf("happen error");
            log.info("{} --> future 第一阶段 result = {}",
                    Thread.currentThread().getName(), result);
            return result;
            // 第一阶段结束后 进入回调钩子, v是第一阶段的结果result, e是第一阶段的异常
        }, pool).whenComplete((v, e) -> {
            // 第一阶段没有异常
            if (e == null) {
                log.info("{} --> 进入回调钩子, 其中第一阶段的结果v = {}",
                        Thread.currentThread().getName(), v);
            } else {
                log.info("{} --> 进入回调钩子, 出现异常 = {}",
                        Thread.currentThread().getName(), e.getMessage());
            }
        }).exceptionally(e -> {
           log.error("{} --> 异常情况 -->  message = {}", Thread.currentThread().getName(),
                   e.getMessage());
           return null;
        });
        log.info("{} --> 主线程执行逻辑", Thread.currentThread().getName());
        Integer result = future.get();
        log.info("{} --> result = {}", Thread.currentThread().getName(), result);
        pool.shutdown();
    }

}

打印结果 2-3-2 :

21:42:14.189 [pool-1-thread-1] INFO com.WhenCompleteDemo - pool-1-thread-1 --> future 第一阶段 coming
21:42:14.189 [main] INFO com.WhenCompleteDemo - main --> 主线程执行逻辑
21:42:16.208 [pool-1-thread-1] INFO com.WhenCompleteDemo - pool-1-thread-1 --> 进入回调钩子, 出现异常 = java.lang.NumberFormatException: For input string: "happen error"
21:42:16.210 [pool-1-thread-1] ERROR com.WhenCompleteDemo - pool-1-thread-1 --> 异常情况 -->  message = java.lang.NumberFormatException: For input string: "happen error"
21:42:16.210 [main] INFO com.WhenCompleteDemo - main --> result = null

Process finished with exit code 0

 

2.4、调用 handle() 方法统一处理异常和结果

 
       除了通过whenComplete、exceptionally 分别设置完成钩子、异常钩子之外,还可以调用handle()方法统一处理结果和异常。计算结果存在依赖关系,使两个线程串行化。由于存在依赖关系,当前步骤异常,就不走下一阶段,直接叫停。

java.util.concurrent.CompletableFuture<T> 中的 handle 方法

    // 在执行任务的同一个线程中处理异常和结果
    public <U> CompletableFuture<U> handle(
        BiFunction<? super T, Throwable, ? extends U> fn) {
        return uniHandleStage(null, fn);
    }

    // 可能不在执行任务的同一个线程中处理异常和结果
    public <U> CompletableFuture<U> handleAsync(
        BiFunction<? super T, Throwable, ? extends U> fn) {
        return uniHandleStage(defaultExecutor(), fn);
    }

    // 在指定线程池executor中处理异常和结果
    public <U> CompletableFuture<U> handleAsync(
        BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) {
        return uniHandleStage(screenExecutor(executor), fn);
    }

示例代码 2-4-1 :

import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;

@Slf4j
public class HandleDemo {

    public static void main(String[] args) {

        ExecutorService pool = Executors.newFixedThreadPool(5);
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            log.info("hello, 进入到 future1 子任务一阶段执行");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int result = ThreadLocalRandom.current().nextInt(10);
            log.info("hi, future1 子任务一阶段执行完成,result = {}", result);
            return result;
        }, pool);
        future1.handle((v, e) -> {
            log.info("hello, 进入到 future1 子任务 handle, v = {}", v);
            if (e == null) {
                v = v * 2;
                log.info("hi, 进入到 future1 子任务 handle 无异常,v = {}", v);
            } else {
                log.info("hi, 进入到 future1 子任务 handle 有异常,v = {}", v);
            }
            return v;
        });

        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            log.info("hello, 进入到 future2 子任务一阶段执行");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int result = ThreadLocalRandom.current().nextInt(10);
            int count = 10/0;
            log.info("hi, future2 子任务一阶段执行完成,result = {}", result);
            return result;
        }, pool).handleAsync((v, e) -> {
            log.info("hello, 进入到 future2 子任务 handle, v = {}", v);
            if (e == null) {
                v = v * 2;
                log.info("hi, 进入到 future2 子任务 handle 无异常,v = {}", v);
            } else {
                log.info("hi, 进入到 future2 子任务 handle 有异常,v = {}", v);
            }
            return v;
        });
        log.info("执行主线程任务完成");
        pool.shutdown();
    }

}

打印结果 2-4-1 :

14:52:12.610 [main] INFO com.HandleDemo - 执行主线程任务完成
14:52:12.610 [pool-1-thread-2] INFO com.HandleDemo - hello, 进入到 future2 子任务一阶段执行
14:52:12.610 [pool-1-thread-1] INFO com.HandleDemo - hello, 进入到 future1 子任务一阶段执行
14:52:15.618 [pool-1-thread-1] INFO com.HandleDemo - hi, future1 子任务一阶段执行完成,result = 8
14:52:15.623 [ForkJoinPool.commonPool-worker-3] INFO com.HandleDemo - hello, 进入到 future2 子任务 handle, v = null
14:52:15.625 [pool-1-thread-1] INFO com.HandleDemo - hello, 进入到 future1 子任务 handle, v = 8
14:52:15.625 [ForkJoinPool.commonPool-worker-3] INFO com.HandleDemo - hi, 进入到 future2 子任务 handle 有异常,v = null
14:52:15.626 [pool-1-thread-1] INFO com.HandleDemo - hi, 进入到 future1 子任务 handle 无异常,v = 16

Process finished with exit code 0

 

2.5、异步任务的串行执行

 
       如果两个异步任务需要串行(一个任务依赖另一个任务)执行,可以通过 CompletionStage 接口的 thenApply()thenAccept()thenRun()thenCompose() 四个方法来实现。

方法入参出参
thenApply()上一个任务所返回结果
thenRun()
thenAccept()上一个任务所返回结果
thenCompose()上一个任务所返回结果CompletionStage异步实例

2.5.1、thenApply() 方法

 
       计算结果存在依赖关系,使两个线程串行化。由于存在依赖关系,当前步骤异常,就不走下一阶段,直接叫停。

java.util.concurrent.CompletableFuture<T> 中的 thenApply 方法

    /**  
     *  后一个任务与前一个任务在同一个线程中执行
	 *  泛型参数 T:上一个任务所返回结果的类型。
	 *  泛型参数 U:当前任务的返回值类型。
	 **/	 
    public <U> CompletableFuture<U> thenApply(
           Function<? super T,? extends U> fn) {
        return uniApplyStage(null, fn);
    }

    /**
     *  后一个任务与前一个任务不在同一个线程中执行
	 *  泛型参数 T:上一个任务所返回结果的类型。
	 *  泛型参数 U:当前任务的返回值类型。
	 **/
    public <U> CompletableFuture<U> thenApplyAsync(
           Function<? super T,? extends U> fn) {
        return uniApplyStage(defaultExecutor(), fn);
    }
	
    /**
     *  后一个任务在指定的executor线程池中执行
	 *  泛型参数 T:上一个任务所返回结果的类型。
	 *  泛型参数 U:当前任务的返回值类型。
	 **/	 
    public <U> CompletableFuture<U> thenApplyAsync(
           Function<? super T,? extends U> fn, Executor executor) {
        return uniApplyStage(screenExecutor(executor), fn);
    }

代码示例 2-5-1-1 :

import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;

@Slf4j
public class ThenApplyDemo {

    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(5);
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            log.info("hello, 进入到 future 任务第一阶段执行");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int result = ThreadLocalRandom.current().nextInt(10);
            log.info("hi, future 任务第一阶段执行完成,result = {}", result);
            return result;
        }, pool);
        future.thenApply((firstValue) -> {
            log.info("hello, 进入到 future 任务第二阶段执行 firstValue = {}", firstValue);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int result = ThreadLocalRandom.current().nextInt(10) + firstValue;
            log.info("hi, future 任务第二阶段执行完成,result = {}", result);
            return "future=" +result;
        }).handle((v, e) -> {
            if (e == null) {
                log.info("hi, 进入到 future 任务 handle 无异常,v = {}", v);
            } else {
                log.info("hi, 进入到 future 任务 handle 有异常,v = {}", v);
            }
            return v;
        });
        log.info("主线程执行逻辑");
        pool.shutdown();
    }

}

执行结果 2-5-1-1 :

15:31:20.879 [main] INFO com.ThenApplyDemo - 主线程执行逻辑
15:31:20.879 [pool-1-thread-1] INFO com.ThenApplyDemo - hello, 进入到 future 任务第一阶段执行
15:31:23.891 [pool-1-thread-1] INFO com.ThenApplyDemo - hi, future 任务第一阶段执行完成,result = 7
15:31:23.894 [pool-1-thread-1] INFO com.ThenApplyDemo - hello, 进入到 future 任务第二阶段执行 firstValue = 7
15:31:26.896 [pool-1-thread-1] INFO com.ThenApplyDemo - hi, future 任务第二阶段执行完成,result = 11
15:31:26.897 [pool-1-thread-1] INFO com.ThenApplyDemo - hi, 进入到 future 任务 handle 无异常,v = future=11

Process finished with exit code 0

 

2.5.2、thenRun() 方法

 
       thenRun()方法与thenApply()方法不同的是,不关心任务的处理结果。只要前一个任务执行完成,就开始执行后一个串行任务。thenRun()既不能接收参数又不支持返回值。异步任务A执行完执行B,并且B不需要A的结果。

java.util.concurrent.CompletableFuture<T> 中的 thenRun 方法

 // 后一个任务与前一个任务在同一个线程中执行
	public CompletableFuture<Void> thenRun(Runnable action) {
        return uniRunStage(null, action);
    }

    // 后一个任务与前一个任务不在同一个线程中执行
    public CompletableFuture<Void> thenRunAsync(Runnable action) {
        return uniRunStage(defaultExecutor(), action);
    }

    // 后一个任务在executor线程池中执行
    public CompletableFuture<Void> thenRunAsync(Runnable action,
                                                Executor executor) {
        return uniRunStage(screenExecutor(executor), action);
    }

 

2.5.3、thenAccept() 方法

 
       调用thenAccept()方法时,后一个任务可以接收(或消费)前一个任务的处理结果,但是后一个任务没有结果输出。接收任务的处理结果,并消费处理,无返回结果。

java.util.concurrent.CompletableFuture 中的 thenAccept 方法

    // 后一个任务与前一个任务在同一个线程中执行
    public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
        return uniAcceptStage(null, action);
    }
    // 后一个任务与前一个任务不在同一个线程中执行
    public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) {
        return uniAcceptStage(defaultExecutor(), action);
    }
    // 后一个任务在指定的executor线程池中执行
    public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action,
                                                   Executor executor) {
        return uniAcceptStage(screenExecutor(executor), action);
    }

 

2.5.4、thenCompose() 方法

 
       thenCompose() 方法,要求第二个任务的返回值是一个 CompletionStage 异步实例。因此,可以调用 CompletableFuture.supplyAsync() 方法将第二个任务所要调用的普通异步方法包装成一个 CompletionStage 异步实例。通过该实例,还可以进行下一轮CompletionStage任务的调度和执行,比如可以持续进行CompletionStage链式(或者流式)调用。(个人感觉这个方法用的比较少)

java.util.concurrent.CompletableFuture<T> 中的 thenCompose 方法

    public <U> CompletableFuture<U> thenCompose(
        Function<? super T, ? extends CompletionStage<U>> fn) {
        return uniComposeStage(null, fn);
    }

    public <U> CompletableFuture<U> thenComposeAsync(
        Function<? super T, ? extends CompletionStage<U>> fn) {
        return uniComposeStage(defaultExecutor(), fn);
    }

    public <U> CompletableFuture<U> thenComposeAsync(
        Function<? super T, ? extends CompletionStage<U>> fn,
        Executor executor) {
        return uniComposeStage(screenExecutor(executor), fn);
    }

代码示例 2-5-4-1 :

import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;

@Slf4j
public class ThenComposeDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(5);
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            log.info("hello, 进入到 future 任务第一阶段执行");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int result = ThreadLocalRandom.current().nextInt(10);
            log.info("hi, future 任务第一阶段执行完成,result = {}", result);
            return result;
        }, pool).thenCompose((firstValue) -> {
            log.info("hello, thenCompose --> firstValue = {}", firstValue);
            return CompletableFuture.supplyAsync(() -> {
                log.info("hello, 进入到 future 任务第二阶段执行");
                int result = ThreadLocalRandom.current().nextInt(10);
                result = result + firstValue;
                log.info("hi, future 任务第二阶段执行完成,result = {}", result);
                return result;
            });
        });
        Integer result = future.get();
        log.info("result -------> {}", result);
        log.info("主线程执行逻辑");
        pool.shutdown();
    }

}

打印结果 2-5-4-1 :

16:02:12.403 [pool-1-thread-1] INFO com.ThenComposeDemo - hello, 进入到 future 任务第一阶段执行
16:02:15.413 [pool-1-thread-1] INFO com.ThenComposeDemo - hi, future 任务第一阶段执行完成,result = 8
16:02:15.415 [pool-1-thread-1] INFO com.ThenComposeDemo - hello, thenCompose --> firstValue = 8
16:02:15.416 [ForkJoinPool.commonPool-worker-3] INFO com.ThenComposeDemo - hello, 进入到 future 任务第二阶段执行
16:02:15.416 [ForkJoinPool.commonPool-worker-3] INFO com.ThenComposeDemo - hi, future 任务第二阶段执行完成,result = 15
16:02:15.416 [main] INFO com.ThenComposeDemo - result -------> 15
16:02:15.416 [main] INFO com.ThenComposeDemo - 主线程执行逻辑

Process finished with exit code 0

 

2.6、异步任务的合并执行

 

2.6.1、thenCombine() 方法

 
       thenCombine() 会在两个 CompletionStage 任务都执行完成后,把两个任务的结果一起交给 thenCombine() 来处理。两个 CompletionStage 任务都完成后,最终能把两个任务的结果一起交给 thenCombine 来处理。先完成的先等着,等待其他分支任务。
java.util.concurrent.CompletableFuture<T> 中的 thenCombine 方法:

    /**
     * 合并代表第二步任务(参数other)的CompletionStage实例,返回第三步任务的CompletionStage
     * @param other 表示待合并的第二步任务的CompletionStage实例
     * @param fn    表示第一个任务和第二个任务执行完成后,第三步需要执行的逻辑
     * @patam <T>   表示第一个任务所返回结果的类型
     * @param <U>   表示第二个任务所返回结果的类型
     * @param <V>   表示第三个任务所返回结果的类型
     * @return
     */
    public <U, V> CompletableFuture<V> thenCombine(
            CompletionStage<? extends U> other,
            BiFunction<? super T, ? super U, ? extends V> fn) {
        return biApplyStage(null, other, fn);
    }

    /**
     * 不一定在同一个线程中执行第三步任务的CompletionStage实例
     * @param other 表示待合并的第二步任务的CompletionStage实例
     * @param fn    表示第一个任务和第二个任务执行完成后,第三步需要执行的逻辑
     * @patam <T>   表示第一个任务所返回结果的类型
     * @param <U>   表示第二个任务所返回结果的类型
     * @param <V>   表示第三个任务所返回结果的类型
     * @return
     */
    public <U, V> CompletableFuture<V> thenCombineAsync(
            CompletionStage<? extends U> other,
            BiFunction<? super T, ? super U, ? extends V> fn) {
        return biApplyStage(defaultExecutor(), other, fn);
    }

    /**
     * 第三步任务的CompletionStage实例在指定的executor线程池中执行
     * @param other 表示待合并的第二步任务的CompletionStage实例
     * @param fn    表示第一个任务和第二个任务执行完成后,第三步需要执行的逻辑
     * @patam <T>   表示第一个任务所返回结果的类型
     * @param <U>   表示第二个任务所返回结果的类型
     * @param <V>   表示第三个任务所返回结果的类型
     * @return
     */
    public <U, V> CompletableFuture<V> thenCombineAsync(
            CompletionStage<? extends U> other,
            BiFunction<? super T, ? super U, ? extends V> fn, Executor executor) {
        return biApplyStage(screenExecutor(executor), other, fn);
    }

代码示例 2-6-1-1 :

import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;

@Slf4j
public class ThenCombineDemo {

    public static void main(String[] args) {

        ExecutorService pool = Executors.newFixedThreadPool(5);
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            log.info("hello, 进入到 future1 任务执行开始");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int result = ThreadLocalRandom.current().nextInt(10);
            log.info("hi, future1 任务执行完成,result = {}", result);
            return result;
        }, pool);
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            log.info("hello, 进入到 future2 任务执行开始");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int result = ThreadLocalRandom.current().nextInt(10);
            log.info("hi, future2 任务执行完成,result = {}", result);
            return result;
        }, pool);
        CompletableFuture<Integer> future3 = future1.thenCombine(future2,(firstValue, secondValue) -> {
            log.info("hello, 进入到 future3 任务执行开始");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int result = ThreadLocalRandom.current().nextInt(10);
            result = result + firstValue + secondValue;
            log.info("hi, future3 任务执行完成,result = {}", result);
            return result;
        }).whenComplete((v, e) -> {
            if (e == null) {
                log.info("进入whenComplete, v = {}", v);
            } else {
                log.info("进入whenComplete 出现异常, v = {}, e", v, e.getMessage());
            }
        });
        log.info("主线程执行逻辑");
        pool.shutdown();
    }

}

执行结果 2-6-1-1 :

16:26:54.758 [main] INFO com.ThenCombineDemo - 主线程执行逻辑
16:26:54.758 [pool-1-thread-2] INFO com.ThenCombineDemo - hello, 进入到 future2 任务执行开始
16:26:54.758 [pool-1-thread-1] INFO com.ThenCombineDemo - hello, 进入到 future1 任务执行开始
16:26:57.778 [pool-1-thread-2] INFO com.ThenCombineDemo - hi, future2 任务执行完成,result = 0
16:26:57.778 [pool-1-thread-1] INFO com.ThenCombineDemo - hi, future1 任务执行完成,result = 5
16:26:57.787 [pool-1-thread-1] INFO com.ThenCombineDemo - hello, 进入到 future3 任务执行开始
16:27:00.795 [pool-1-thread-1] INFO com.ThenCombineDemo - hi, future3 任务执行完成,result = 9
16:27:00.795 [pool-1-thread-1] INFO com.ThenCombineDemo - 进入whenComplete, v = 9

Process finished with exit code 0

 

2.6.2、runAfterBoth() 方法

 
runAfterBoth() 方法不关心每一步任务的输入参数和处理结果.

java.util.concurrent.CompletableFuture<T> 中的 runAfterBoth 方法

	// 合并第二步任务的CompletionStage实例,返回第三步任务的
    public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other,
                                                Runnable action) {
        return biRunStage(null, other, action);
    }
    // 不一定在同一个线程中执行第三步任务的CompletionStage实例
    public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,
                                                     Runnable action) {
        return biRunStage(defaultExecutor(), other, action);
    }
    // 第三步任务的CompletionStage实例在指定的executor线程池中执行
    public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,
                                                     Runnable action,
                                                     Executor executor) {
        return biRunStage(screenExecutor(executor), other, action);
    }

 

2.6.3、thenAcceptBoth() 方法

 
       调用thenAcceptBoth()方法,第三个任务可以接收其合并过来的第一个任务、第二个任务的处理结果,但是第三个任务(合并任务)却不能返回结果。

java.util.concurrent.CompletableFuture<T> 中的 thenAcceptBoth 方法

    // 合并第二步任务的CompletionStage实例,返回第三步任务的CompletionStage
    public <U> CompletableFuture<Void> thenAcceptBoth(
        CompletionStage<? extends U> other,
        BiConsumer<? super T, ? super U> action) {
        return biAcceptStage(null, other, action);
    }
    // 功能与上一个方法相同,不一定在同一个线程执行第三步任务
    public <U> CompletableFuture<Void> thenAcceptBothAsync(
        CompletionStage<? extends U> other,
        BiConsumer<? super T, ? super U> action) {
        return biAcceptStage(defaultExecutor(), other, action);
    }
    // 功能与上一个方法相同,在指定的executor线程池中执行第三步任务
    public <U> CompletableFuture<Void> thenAcceptBothAsync(
        CompletionStage<? extends U> other,
        BiConsumer<? super T, ? super U> action, Executor executor) {
        return biAcceptStage(screenExecutor(executor), other, action);
    }

 

2.6.4、allOf() 方法等待所有的任务结束

 
       allOf()会等待所有的任务结束,以合并所有的任务。thenCombine()只能合并两个任务,如果需要合并多个异步任务,那么可以调用allOf()

java.util.concurrent.CompletableFuture<T> 中的 allOf 方法

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
        return andTree(cfs, 0, cfs.length - 1);
}

代码示例 2-6-4-1 :

import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;

@Slf4j
public class AllOfDemo {


    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(5);
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            log.info("hello, 进入到 future1 任务执行开始");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int result = ThreadLocalRandom.current().nextInt(10);
            log.info("hi, future1 任务执行完成,result = {}", result);
            return result;
        }, pool);
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            log.info("hello, 进入到 future2 任务执行开始");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int result = ThreadLocalRandom.current().nextInt(10);
            log.info("hi, future2 任务执行完成,result = {}", result);
            return result;
        }, pool);
        CompletableFuture<Integer> future3 = CompletableFuture.supplyAsync(() -> {
            log.info("hello, 进入到 future3 任务执行开始");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int result = ThreadLocalRandom.current().nextInt(10);
            log.info("hi, future3 任务执行完成,result = {}", result);
            return result;
        }, pool);
        CompletableFuture<Void> futureAll = CompletableFuture.allOf(future1,future2,future3).whenComplete((v, e) -> {
            if (e == null) {
                log.info("进入whenComplete, v = {}", v);
            } else {
                log.info("进入whenComplete 出现异常, v = {}, e", v, e.getMessage());
            }
        });
        log.info("主线程执行逻辑");
        pool.shutdown();
    }
}

打印结果 2-6-4-1 :

16:42:51.635 [main] INFO com.AllOfDemo - 主线程执行逻辑
16:42:51.635 [pool-1-thread-1] INFO com.AllOfDemo - hello, 进入到 future1 任务执行开始
16:42:51.635 [pool-1-thread-2] INFO com.AllOfDemo - hello, 进入到 future2 任务执行开始
16:42:51.635 [pool-1-thread-3] INFO com.AllOfDemo - hello, 进入到 future3 任务执行开始
16:42:54.645 [pool-1-thread-3] INFO com.AllOfDemo - hi, future3 任务执行完成,result = 7
16:42:54.645 [pool-1-thread-1] INFO com.AllOfDemo - hi, future1 任务执行完成,result = 3
16:42:54.645 [pool-1-thread-2] INFO com.AllOfDemo - hi, future2 任务执行完成,result = 2
16:42:54.651 [pool-1-thread-3] INFO com.AllOfDemo - 进入whenComplete, v = null

Process finished with exit code 0

 

2.7、异步任务的选择执行

 
        所谓选择执行,就是看谁的速度快,前面两个并行任务,谁的结果返回速度快,谁的结果将作为第三步任务的输入。对两个异步任务的选择可以通过 CompletionStage 接口的 applyToEither()runAfterEither()acceptEither() 三个方法来实现。这三个方法的不同之处在于它的核心参数的类型不同,分别为 Function<T,R>RunnableConsumer<? superT> 类型。
 

2.7.1、applyToEither() 方法

 
       两个 CompletionStage 谁返回结果的速度快,applyToEither() 方法就用这个最快的 CompletionStage 的结果,进行下一步(第三步)的回调操作。

java.util.concurrent.CompletableFuture<T> 中的 applyToEither 方法

    // 和other任务进行速度PK,最快返回的结果用于执行fn回调函数
    public <U> CompletableFuture<U> applyToEither(
        CompletionStage<? extends T> other, Function<? super T, U> fn) {
        return orApplyStage(null, other, fn);
    }
    // 功能与上一个方法相同,不一定在同一个线程中执行fn回调函数
    public <U> CompletableFuture<U> applyToEitherAsync(
        CompletionStage<? extends T> other, Function<? super T, U> fn) {
        return orApplyStage(defaultExecutor(), other, fn);
    }
    // 功能与上一个方法相同,在指定线程执行fn回调函数
    public <U> CompletableFuture<U> applyToEitherAsync(
        CompletionStage<? extends T> other, Function<? super T, U> fn,
        Executor executor) {
        return orApplyStage(screenExecutor(executor), other, fn);
    }

 

2.7.2、runAfterEither() 方法

 
       runAfterEither() 方法的功能为:前面两个 CompletionStage 实例,任何一个完成了都会执行第三步回调操作。三个任务的回调函数都是 Runnable 类型的。

java.util.concurrent.CompletableFuture<T> 中的 runAfterEither 方法

    // 和other任务进行速度PK,只要一个执行完成,就开始执行action回调函数
    public CompletableFuture<Void> runAfterEither(CompletionStage<?> other,
                                                  Runnable action) {
        return orRunStage(null, other, action);
    }
    // 功能与上一个方法相同,不一定在同一个线程中执行action回调函数
    public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,
                                                       Runnable action) {
        return orRunStage(defaultExecutor(), other, action);
    }
    // 功能与上一个方法相同,在指定线程执行action回调函数
    public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,
                                                       Runnable action,
                                                       Executor executor) {
        return orRunStage(screenExecutor(executor), other, action);
    }

 

2.7.3、acceptEither() 方法

 
       调用 acceptEither() 方法,两个 CompletionStage 谁返回结果的速度快,acceptEither() 就用那个最快的 CompletionStage 的结果作为下一步(第三步)的输入,但是第三步没有输出。

java.util.concurrent.CompletableFuture<T> 中的 acceptEither 方法

    // 和other任务进行速度PK,最快返回的结果用于执行action回调函数
    public CompletableFuture<Void> acceptEither(
        CompletionStage<? extends T> other, Consumer<? super T> action) {
        return orAcceptStage(null, other, action);
    }
    // 功能与上一个方法相同,不一定在同一个线程中执行action回调函数
    public CompletableFuture<Void> acceptEitherAsync(
        CompletionStage<? extends T> other, Consumer<? super T> action) {
        return orAcceptStage(defaultExecutor(), other, action);
    }
    // /功能与上一个方法相同,在指定的executor线程池中执行第三步任务
    public CompletableFuture<Void> acceptEitherAsync(
        CompletionStage<? extends T> other, Consumer<? super T> action,
        Executor executor) {
        return orAcceptStage(screenExecutor(executor), other, action);
    }

 

2.8、CompletableFuture 的基础方法

 

说明方法备注
获得结果get()不见不散(阻塞),get() 方法在编译期间会去做检查异常的工作,需要捕获/抛出异常
获得结果get(long timeout, TimeUnit unit)过时不候
获得结果join()join() 方法在编译期间不会去做检查异常的工作
获得结果getNow(T valueIfAbsent)异步任务计算完,返回异步任务的计算结果。异步任务没有计算完,返回设定的 valueIfAbsent
主动触发计算boolean complete(T value)是否打断get() 方法,立即返回入参中的值

 
 
 
 
 
 
 
.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/774146.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

重温黑盒、白盒与灰盒测试方法

黑盒、白盒和灰盒测试方法是软件测试中常用的测试策略&#xff0c;用于评估系统的功能和质量。 对于黑盒、白盒与灰盒测试方法的理解&#xff0c;几年前我在某乎做过一个概念性的回答&#xff0c;当时提问者询问&#xff1a;如何跟非技术人员解释黑盒、白盒、灰盒测试的区别&a…

UG\NX二次开发 使用exception类,异常处理的方法

文章作者:里海 来源网站:https://blog.csdn.net/WangPaiFeiXingYuan 简介 异常处理是一种编程中常见的错误处理机制,它允许程序在遇到错误或异常情况时优雅地处理。在C++中,异常类是一种重要的异常处理工具。 当程序发生错误或异常时,我们可以使用excepti…

查看maven包依赖关系,一行命令搞定。

1&#xff1a;以logback-classic包为例&#xff0c;在命令端运行&#xff0c;下面命令 mvn dependency:tree -Dincludesch.qos.logback:logback-classic 2.会出现以下日志&#xff0c;就可以清楚的知道这个jar包&#xff0c;是在谁的下面。

python 批量下载图片(协程)

要下载的图片网站 1、总共多少页&#xff0c;得到每页的 url 列表 2、每页的图片详情的 ulr 列表&#xff08;因为该高清大图在图片详情页&#xff0c;因此需要去图片详情页拿图片的url) ​​​​​​​ 3、进入图片详情页&#xff0c;获取到图片url 然后下载。 完整代码如下&…

玩转代码|详细盘点JavaScript 数据类型

目录 什么是JavaScript JavaScript 拥有动态类型 JavaScript 字符串 JavaScript 数字 JavaScript 布尔 JavaScript 数组 JavaScript 对象 Undefined 和 Null JS 中如何判断 undefined JS 中如何判断 null 声明变量类型 什么是JavaScript JavaScript&#xff08;简称…

“nacos is starting with cluster”之nacos启动报错问题

下载并解压nacos后&#xff0c;通过点击startup.cmd启动nacos&#xff0c;出现nacos is starting with cluster的错误&#xff0c;导致nacos未能启动成功。 这是因为&#xff0c;通过startup.cmd命令启动nacos&#xff0c;默认是以集群的方式进行启动的&#xff0c;我们可以改…

为3.7亿用户提供优质服务的微众银行,如何保障应用安全、及时上线

微众银行成立于2014年&#xff0c;是国内首家数字银行。作为银行业改革创新的产物&#xff0c;开业八年多来&#xff0c;微众银行积极把握数字经济时代发展新机遇&#xff0c;运用科技手段为小微企业及普罗大众提供特色化、差异化的优质金融服务&#xff0c;在以数字普惠金融服…

地下供水管漏水监测-供水管道漏水监测设备

地下供水管道作为城市供水系统的重要组成部分&#xff0c;承载着为居民和企业提供清洁饮用水的重要使命。然而&#xff0c;由于管道老化、施工质量、外力损伤等因素&#xff0c;地下供水管道泄漏问题时有发生&#xff0c;这不仅造成了宝贵的水资源浪费&#xff0c;还会导致供水…

流程编排及可视化

写在前面 这里只介绍liteflow的简单基础使用以及作者对liteflow进行可视化扩展的相关阐述 一、背景及意义 背景&#xff1a;对于拥有复杂业务逻辑的系统承载着核心业务逻辑&#xff0c;这些核心业务逻辑涉及内部逻辑运算&#xff0c;缓存操作&#xff0c;持久化操作&#xf…

LiveQing视频点播RTMP推流直播功能-点播拉转在线资源拉转转推到鉴权直播间云端录像集中录像存储

LiveQing点播拉转在线资源拉转转推到鉴权直播间云端录像集中录像存储 1、基本功能2、拉转直播2.1、点播资源拉转2.2、在线资源拉转2.3、服务器本地文件拉转 3、拉转直播如何录像&#xff1f;4、RTMP推流视频直播和点播流媒体服务 1、基本功能 LiveQing RTMP直播点播流媒体服务…

Electron运行报错: Failed to fetch extension, trying ...

Script: "electron:serve": "vue-cli-service electron:serve", 运行 npm run electron:serve 时报错&#xff1a; 解决方法&#xff1a; 检查你的electron配置文件也就是 vue.config.js 中的 mian 的文件 注释其中关于开发工具安装的部分&#xff1a;…

搭建zyplayer-doc个人WIKI文档管理工具,问题记录及简单使用

目录 项目简介各模块介绍项目部署准备工作修改配置及数据库初始化 编译部署编译后文件前后端在同一个部署包当中&#xff08;无需单独部署前端&#xff09; 环境部署目录规划启动脚本编写登录 部署问题记录错误: 找不到或无法加载主类Failed to instantiate [javax.sql.DataSou…

Linux--标记位:flag

我们知道&#xff0c;标记位赋予的值不同&#xff0c;就会生成不同的选项。那么如何给一个变量的位置赋予多个值呢&#xff1f; int整型有32个比特位&#xff0c;故我们可以通过改变位的方式改变值的大小 示例&#xff1a; #include <stdio.h> #include <unistd.h&…

Pandas数据分析库详解

概要 Pandas是一个基于 NumPy 的非常强大的开源数据处理库&#xff0c;它提供了高效、灵活和丰富的数据结构和数据分析工具&#xff0c;当涉及到数据分析和处理时&#xff0c;使得数据清洗、转换、分析和可视化变得更加简单和高效。本文中&#xff0c;我们将学习如何使用Panda…

ABeam News | 聚智同行,制胜未来,ABeam Consulting出席2023思爱普中国峰会

News 6月14日&#xff0c;2023思爱普中国峰会在北京盛大召开。本届峰会以“创新赋能高质量发展”为主题&#xff0c;现场汇聚了业界大咖、行业专家和众多客户伙伴&#xff0c;聚焦数字化加速、全球化出海、可持续发展等主题。ABeam Consulting作为SAP金牌合作伙伴及本次峰会的…

MDK版本坑error: A1167E: Invalid line start

移植threadx时对于.s的汇编文件报了大量错误&#xff0c;到文件里查看是汇编文件中的注释使用的C的注释方法&#xff0c;导致大量报错 MDK官网查到原因&#xff0c;一个是版本问题&#xff0c;一个是设置问题&#xff0c; https://developer.arm.com/documentation/ka002247…

mac端好用的多功能音频软件 AVTouchBar for mac 3.0.7

AVTouchBar是来自触摸栏的视听播放器&#xff0c;将跳动笔记的内容带到触摸栏&#xff0c;触摸栏可显示有趣的音频内容&#xff0c;拥有更多乐趣&#xff0c;以一种有趣的方式播放音乐&#xff0c;该软件支持多种音频播放软件&#xff0c;可在Mac上自动更改音乐~ 音频选择-与内…

刷个宇宙题:剑指 Offer II 006. 排序数组中两个数字之和、 007. 数组中和为 0 的三个数

题目 006. 排序数组中两个数字之和 方法1&#xff1a;哈希表的方式 class Solution { public:vector<int> twoSum(vector<int>& numbers, int target) {//存一个key-value (值&#xff0c;index)unordered_map<int, int> ValueMap;int i 0;for(auto nu…

【内网自制无需密码的SSL证书--适用与IP或者localhost】

内网自制无需密码的SSL证书--适用与IP或者localhost 前言步骤确认是否安装openssl自制CA私钥自制csr文件免除密码自制CA证书 验证 前言 搞半死&#xff0c;原来这么简单&#xff0c;今天就把教程分享给大家&#xff0c;本文基于CentOS7环境的openssl生成SSL自制证书&#xff0…

项目实战Qt网盘系统

背景&#xff1a;随着时代的发展&#xff0c;业务数据量的剧增及移动办公需求&#xff0c;人们对内存的需求越来越强&#xff0c;传统的存储产品&#xff0c;在容量及携带型日益不能满足人工的工作需求&#xff0c;网盘再此背景下应运而生。网盘是能够提供文件同步&#xff0c;…