当前位置: 首页 > news >正文

多线程-CompletableFuture

简介

CompletableFuture:异步任务编排工具。java 8中引入的一个类,位于juc包下,是Future的增强版。它可以让用户更好地构建和组合异步任务,避免回调地狱。

在CompletableFuture中,如果用户没有指定执行异步任务时的线程池,默认使用ForkJoinPool中的公共线程池。

使用案例

简单使用

几个入门案例,学习如何使用CompletableFuture提交异步任务并行接收返回值

提交异步任务 supplyAsync

提交一个异步任务,然后阻塞地获取它的返回值

@Test
public void test1() throws ExecutionException, InterruptedException {// 提交异步任务CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}return "[" + Thread.currentThread().getName() + "] " + "task1";});String s = future.get();  // 阻塞地获取异步任务的执行结果System.out.println("s = " + s);  // [ForkJoinPool.commonPool-worker-9] task1
}

阻塞地等待异步任务完成 join

join方法和get方法,都是阻塞地等待异步任务执行完成,然后获取返回值,只不过对于异常的处理不同,推荐使用get方法来获取返回值。

@Test
public void test2() throws ExecutionException, InterruptedException {CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}return "[" + Thread.currentThread().getName() + "] " + "task2";});System.out.println(future.get());  // [ForkJoinPool.commonPool-worker-9] task2
}

消费异步任务的结果 thenAccept

thenAccept函数接收一个Consumer类型的实例作为参数,它会消费异步任务的执行结果,然后返回一个CompletableFuture<Void>,实际上没有必要处理它的返回值。

@Test
public void test8() throws InterruptedException {// 异步任务CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}return "[" + Thread.currentThread().getName() + "] " + "task2";});// 使用thenAccept,提供一个回调函数,消费异步任务的结果future.join();future.thenAccept(System.out::println);  // [ForkJoinPool.commonPool-worker-9] task2
}

异步任务编排

到这里开始,开始涉及到异步任务编排,在一个异步任务之后启动另一个异步任务,或者在多个异步任务之后另一个异步任务。

具有依赖关系的异步任务,根据异步任务的依赖数量,可以把异步任务分为 零依赖、一元依赖、二元依赖和多元依赖。

一元依赖

thenApply

thenApply方法,接收一个Function作为参数,返回另一个future。一个异步任务依赖另一个异步任务

@Test
public void test3() {// 异步任务1CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}return "[" + Thread.currentThread().getName() + "] " + "[" + (System.currentTimeMillis() / 1000) + "]" + "任务1";});// 异步任务2CompletableFuture<String> future1 = future.thenApply(s -> {try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}return s + "," + "[" + (System.currentTimeMillis() / 1000) + "]" +  "任务2";});future1.join();// 注意结果中异步任务1的时间和异步任务2的时间,说明异步任务2是在异步任务执行完之后触发的// [ForkJoinPool.commonPool-worker-1] [1732975152]任务1,[1732975155]任务2future1.thenAccept(System.out::println);
}

thenAccept方法接收一个Consumer,没有返回值,thenApply接收一个Function,有返回值

thenCompose

从语义上看,thenCompose方法接收一个异步任务作为参数,thenApply方法接收一个普通任务作为参数,选择 thenApply还是 thenCompose 取决于用户的需求,如果新任务是另一个异步任务,选择thenCompose,如果新任务只是消费上一个异步任务的结果,然后返回消费结果,选择thenApply。

@Test
public void test9() {// 异步任务1CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}return "[" + Thread.currentThread().getName() + "] " +"[" + (System.currentTimeMillis() / 1000) + "]" + "任务1";});// 异步任务2CompletableFuture<String> future1 = future.thenCompose(s -> CompletableFuture.supplyAsync(() -> {try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}return s + "\n" +"[" + Thread.currentThread().getName() + "] " +"[" + (System.currentTimeMillis() / 1000) + "]" + "任务2";}));future1.join();//[ForkJoinPool.commonPool-worker-9] [1727663353]任务1//[ForkJoinPool.commonPool-worker-9] [1727663356]任务2future1.thenAccept(System.out::println);
}

二元依赖

thenCombine

融合当前异步任务(调用thenCombine的CF实例)和另一个异步任务(other)的结果,thenCombine() 会在两个任务都执行完成后,把两个任务的结果合并。两个任务是并行执行的,它们之间并没有先后依赖顺序。

@Test
public void test9() {System.out.println("[" + Thread.currentThread().getName() + "] " +"[" + System.currentTimeMillis() / 1000 + "]" + "任务0");// 异步任务1CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}return "[" + Thread.currentThread().getName() + "] " +"[" + System.currentTimeMillis() / 1000 + "]" + "任务1";});// 异步任务2CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}return "[" + Thread.currentThread().getName() + "] " +"[" + System.currentTimeMillis() / 1000 + "]" + "任务2";});// 融合异步任务1、2的结果,生成任务3CompletableFuture<String> future3 = future.thenCombine(future2,(s1, s2) -> {try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}return s1 + "\n" + s2 + "\n" +"[" + Thread.currentThread().getName() + "] " +"[" + System.currentTimeMillis() / 1000 + "]" + "任务3";});future3.join();// 任务1暂停5秒、任务2暂停2秒、任务3暂停3秒,任务3依赖任务1和任务2,可以看到,// 任务1和任务2的执行是互不影响的,等到任务1、任务2都执行完成,任务3才执行// [main] [1734753154]任务0// [ForkJoinPool.commonPool-worker-1] [1734753159]任务1// [ForkJoinPool.commonPool-worker-2] [1734753156]任务2// [ForkJoinPool.commonPool-worker-1] [1734753162]任务3future3.thenAccept(System.out::println);
}

多元依赖

allOf

allOf方法:多个异步任务都完成,才执行下一个异步任务

// allOf 方法接受一个 CompletableFuture 的可变参数列表,并返回一个新的 CompletableFuture,
// 当所有给定的 CompletableFuture 都完成时,新的 CompletableFuture 才会完成。这个方法通常
// 用于等待多个异步任务都完成。
@Test
public void test5() {CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}return "[" + Thread.currentThread().getName() + "] " + "[" + System.currentTimeMillis() / 1000 + "]" + "任务1";});CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}return "[" + Thread.currentThread().getName() + "] " + "[" + System.currentTimeMillis() / 1000 + "]" + "任务2";});// allOf方法,创建一个新的future,它会等到前面两个异步任务完成后再执行CompletableFuture<Void> future3 = CompletableFuture.allOf(future, future2);CompletableFuture<String> future4 = future3.thenApply(v -> {// 在allOf方法返回的异步任务中,它需要调用join方法,来获取之前异步任务的结果String s1 = future.join();String s2 = future2.join();try {Thread.sleep(4000);} catch (InterruptedException e) {throw new RuntimeException(e);}return s1 + "\n" + s2 + "\n" + "[" + Thread.currentThread().getName() + "] " + "[" + System.currentTimeMillis() / 1000 + "]" + "任务3";});future4.join(); // 阻塞当前线程future4.thenAccept(System.out::println);
}
anyOf

多个异步任务中只要有一个完成,就执行下一个异步任务

// anyOf方法
@Test
public void test6() {CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}return "[" + Thread.currentThread().getName() + "] " + "[" + System.currentTimeMillis() / 1000 + "]" + "任务1";});CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}return "[" + Thread.currentThread().getName() + "] " + "[" + System.currentTimeMillis() / 1000 + "]" + "任务2";});CompletableFuture<Object> future3 = CompletableFuture.anyOf(future, future2);future3.join();// 任务2比任务1先结束,所以这里只获取到了任务2的结果// [ForkJoinPool.commonPool-worker-2] [1727662633]任务2future3.thenAccept(System.out::println);
}

异常处理

handle

handle 方法是一个双目方法,它接受一个 BiFunction 参数,该函数有两个参数:一个是异步任务的结果,另一个是异常对象。无论 CompletableFuture 正常完成还是异常完成,都会调用 handle 方法。

正常完成:

@Test
public void test7() {CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}return "[" + Thread.currentThread().getName() + "] " +"[" + System.currentTimeMillis() / 1000 + "]" + "任务1";});CompletableFuture<String> future2 = future.handle((s, e) -> {if (e != null) {return "Error: " + e.getMessage();}return s + " handle";});future2.join();// [ForkJoinPool.commonPool-worker-9] [1727662978]任务1 handlefuture2.thenAccept(System.out::println);
}

exceptionally

exceptionally 方法是一个单目方法,它接受一个 Function 参数,该函数只处理异常情况。当 CompletableFuture异常完成时,exceptionally 方法会被调用。

@Test
public void test8() {CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(1000);int i = 10 / 0;} catch (InterruptedException e) {throw new RuntimeException(e);}return null;});CompletableFuture<String> future2 = future.exceptionally(e -> "Error: " + e.getMessage());future2.join();// Error: java.lang.ArithmeticException: / by zerofuture2.thenAccept(System.out::println);
}

API总结:

  • 零依赖:supplyAsync,直接提交

  • 一元依赖:thenApply、thenAccept、thenCompose

  • 二元依赖:thenCombine

  • 多元依赖:allOf、anyOf

使用时的注意事项

1、指定线程池:使用案例中的代码都没有指定线程池,但是实际使用过程中,最好指定线程池。

2、带有Async后缀的方法:CompletableFuture中提供的API,带有Async后缀的方法和普通方法,例如,thenApply和thenApplyAsync,thenApply使用和上一个任务一样的线程来执行异步任务,thenApplyAsync则使用新线程来执行异步任务,推荐使用带有Async后缀的方法。

源码分析

CompletableFuture使用一个栈来存储当前异步任务之后要触发的任务,栈使用的数据结构是单向链表。调用thenApply等方法编排异步任务时,实际上是向上一个任务的stack属性中注入当前任务,上一个任务结束后,会获取stack属性中的任务,然后继续执行,依次类推,直到stack属性为空

整体结构

CompletableFuture的继承体系

它实现了Future接口和CompletionStage接口:

  • Future代表异步计算的结果,只能通过阻塞或者轮询的方式获取结果,而且不支持设置回调方法
  • CompletionStage代表异步计算的阶段,定义了在异步任务完成之后要触发的操作,这个操作可以是一个普通任务,也可以是一个异步任务

Future:

public interface Future<V> {// 取消异步任务boolean cancel(boolean mayInterruptIfRunning);// 判断异步任务是否取消boolean isCancelled();// 判断异步任务是否结束boolean isDone();// 获取异步任务的结果V get() throws InterruptedException, ExecutionException;// 获取异步任务的结果,并且指定超时时间V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}

CompletionStage:代表异步计算的一个阶段,定义了在一个异步任务完成之后执行的操作,之前在入门案例中演示的thenApply方法、thenCompose方法,就是定义在这个接口中

public interface CompletionStage<T> {/* 一元依赖的异步任务 */// 创建一个新的异步任务,使用Function接口来消费异步任务的结果public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);// 创建一个新的异步任务,使用Consumer接口来消费异步任务的结果public CompletionStage<Void> thenAccept(Consumer<? super T> action);// 创建一个新的异步任务public <U> CompletionStage<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);// ... 
}

CompletionFuture中的属性

CompletionFuture中只有两个属性:result、stack,result存储当前异步任务的结果,stack存储下一个要被触发的异步任务,stack是一个单向链表

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {/* 下面两个成员变量都是通过cas算法来操作的,所有的方法都是在操作这两个变量,通过这两个变量实现了异步任务编排 */volatile Object result;  // 存储当前异步任务的结果// 存储当前异步任务结束后需要触发的后续操作,它是一个单向链表,// 相当于观察者模式中的观察者,当前的CompletableFuture实例就是被观察者(主题)volatile Completion stack; // 静态内部类,要执行的操作abstract static class Completion extends ForkJoinTask<Void>// AsynchronousCompletionTask,没有方法的接口,用于debug时标识一个异步任务implements Runnable, AsynchronousCompletionTask {volatile Completion next;      // 单向链表,指向下一个异步任务// 尝试触发异步任务abstract CompletableFuture<?> tryFire(int mode);}
}

stack属性的数据结构 Completion

Completion是CompletableFuture的静态内部类,可以把它理解为是异步任务要执行的操作,它存储了:

  • 操作,也就是用户要异步执行的任务

  • 操作对应的CompletableFuture实例

  • 操作依赖的CompletableFuture实例

  • 操作完成后需要触发的CompletableFuture实例

Completion比较复杂的地方在于它有多个子类,每个子类都有不同的功能,在前面使用案例中演示的每个API,它的内部都使用了不同的子类,代表不同的操作,所以这里介绍一下Completion和它的继承体系

Completion:顶层类,只定义了指向下一个异步任务的属性 next

// Completion,静态内部类,在继承体系中位于顶层
abstract static class Completion extends ForkJoinTask<Void>implements Runnable, AsynchronousCompletionTask {volatile Completion next;      // 下一个异步任务
}// 一个没有方法的接口,用于标识某个被async方法创建的实例是异步任务,用于debug、监控
public static interface AsynchronousCompletionTask { }

有一个依赖的异步任务:UniCompletion、UniApply,UniCompletion是基类,存储了单个依赖的共有属性,UniApply是具体实现,是thenApply方法对应的内部类。

abstract static class UniCompletion<T,V> extends Completion {Executor executor;                 // 线程池// 当前异步任务对应的CompletableFuture实例CompletableFuture<V> dep;// 当前任务的依赖。具体而言,就是只有在src对应的异步任务执行完成后,才可以执行dep对应的异步任务,也就是当前任务,// 执行异步任务的逻辑中会判断前一个异步任务是否完成,并且获取它的结果。CompletableFuture<T> src;UniCompletion(Executor executor, CompletableFuture<V> dep,CompletableFuture<T> src) {this.executor = executor; this.dep = dep; this.src = src;}// 确保异步任务只执行一次,它会通过cas算法修改状态变量,final boolean claim() {Executor e = executor;if (compareAndSetForkJoinTaskTag((short)0, (short)1)) { // 这个方法继承自ForkJoinTaskif (e == null)return true;executor = null; // disablee.execute(this);}return false;}// 判断当前异步任务是否还存活:dep不为null,就是还存活final boolean isLive() { return dep != null; }
}// UniApply,UniCompletion的子类,定义了异步任务的执行逻辑,thenApply方法对应的内部类,
static final class UniApply<T,V> extends UniCompletion<T,V> {Function<? super T,? extends V> fn;  // 要异步执行的任务
}

有两个依赖的异步任务:BiCompletion、BiApply,类似于上面提到的,BiCompletion是基类,存储了共有属性,BiApply是具体实现

// BiCompletion
abstract static class BiCompletion<T,U,V> extends UniCompletion<T,V> {CompletableFuture<U> snd; // 异步任务的第二个依赖
}// BiApply
static final class BiApply<T,U,V> extends BiCompletion<T,U,V> {BiFunction<? super T,? super U,? extends V> fn;  // 要异步执行的任务
}

AsyncSupply:不属于Completion的继承体系,存储没有依赖的单个异步任务

static final class AsyncSupply<T> extends ForkJoinTask<Void>implements Runnable, AsynchronousCompletionTask {CompletableFuture<T> dep;  // 异步任务对应的CompletableFuture实例Supplier<T> fn;            // 异步任务要执行的计算
}

总结

在介绍CompletableFuture的工作机制之前,先总结一下它的数据结构:

CompletableFuture:代表一个异步任务,它存储了这个异步任务的执行结果、执行完之后要触发的下一个异步任务,同时提供了面向用户的API

  • 它的属性 Object result、Completion stack

Completion:存储了一个异步任务执行过程中需要用到的全部信息,包括它的依赖、它要触发的下一个异步任务、异步任务要执行的计算、异步任务对应的CompletableFuture实例

  • 它的属性:

    • Function function:异步任务要进行的计算

    • CompletableFuture dep:异步任务对应的CompletableFuture实例

    • CompletableFuture src:异步任务的第一个依赖

    • CompletableFuture snd:异步任务的第二个依赖,// 一元依赖的异步任务中没有这个属性

    • CompletableFuture next:要被触发的下一个异步任务

可以看到,CompletableFuture和Completion互相持有对方的实例。

在了解了CompletableFuture的基本结构之后,接下来的问题是:

  • 两个有依赖关系的异步任务是如何被编排在一起的?
  • 上一个异步任务结束之后如何触发下一个异步任务?

哪个线程执行异步任务?

先了解一下异步任务在哪个线程执行。

CompletableFuture使用一个线程池来执行异步任务。用户提交异步任务后,如果没有指定线程池,那么使用默认线程池。如果当前环境下可用cpu核心数大于1,使用的线程池是ForkJoinPool,否则每次提交任务它都会创建一个新的线程(ThreadPerTaskExecutor)

确认使用哪个线程池的源码:

  • ForkJoinPool中公共线程池的并行度默认是CPU个数减1,用户也可以通过jvm参数指定
  • 如果公共线程池的并行度小于等于0,使用单线程线程池
// 判断使用哪个线程池
private static final boolean useCommonPool = (ForkJoinPool.getCommonPoolParallelism() > 1);
private static final Executor asyncPool = useCommonPool ?ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();/* 一、ForkJoinPool中判断当前公共线程池的并行度: */
// 1. 首先从环境变量中获取一个指定的属性 parallelism
String pp = System.getProperty("java.util.concurrent.ForkJoinPool.common.parallelism");
int parallelism = -1;
if (pp != null) {parallelism = Integer.parseInt(pp);
}
// 2. 如果属性为空,获取当前环境下的CPU数个数,如果小于等于1个,那么公共线程池的并行度就是1,否则就是CPU数减1
if (parallelism < 0 && // default 1 less than #cores(parallelism = Runtime.getRuntime().availableProcessors() - 1) <= 0) {parallelism = 1;
}// 二、单线程线程池,它的实现特别简单,为每个任务创建一个新的线程
static final class ThreadPerTaskExecutor implements Executor {public void execute(Runnable r) { new Thread(r).start(); }
}

工作机制

单个异步任务的执行

提交异步任务 supplyAsync方法

整体流程:

// 整体流程:这里省略了前面的方法调用
static <U> CompletableFuture<U> asyncSupplyStage(Executor e,Supplier<U> f) {if (f == null) throw new NullPointerException();// 第一步:创建CompletableFuture实例,向线程池提交任务CompletableFuture<U> d = new CompletableFuture<U>();// 第二步:线程池执行异步任务,异步任务编排就实现在AsyncSupply中,这里专门针对supplyAsync的内部类e.execute(new AsyncSupply<U>(d, f));// 第三步:返回CompletableFuture实例return d;
}

整体流程中的第二步:执行异步任务

// 要点1:AsyncSupply,用户调用supplyAsync方法时执行的内部类,异步任务编排的核心流程
static final class AsyncSupply<T> extends ForkJoinTask<Void>implements Runnable, AsynchronousCompletionTask {CompletableFuture<T> dep;   // 当前CompletableFuture的实例Supplier<T> fn;             // 需要在线程池中执行的任务// 参数1,当前CompletableFuture的实例,用于存储异步任务的返回值并且触发下一个异步任务// 参数2,需要在线程池中执行的任务AsyncSupply(CompletableFuture<T> dep, Supplier<T> fn) {this.dep = dep; this.fn = fn;}// 线程启动后会执行当前实例的run方法,因为它继承了Runnable接口public void run() {CompletableFuture<T> d; Supplier<T> f;if ((d = dep) != null && (f = fn) != null) {dep = null; fn = null;if (d.result == null) {try {// 这里做了两个事情:// 1. 执行异步任务,也就是Supplier接口中提供的函数// 2. 将异步任务的返回值设置到当前CompletableFuture实例中d.completeValue(f.get());} catch (Throwable ex) {// 如果出现异常,设置异常返回值d.completeThrowable(ex);}}// 触发下一个异步任务d.postComplete();}}
}

可以看到,AsyncSupply类继承了Runnable接口,持有当前CompletableFuture实例和要异步执行的任务,在run方法中,执行任务,然后调用CompletableFuture实例中的completeValue方法,设置返回值。

返回值如何被设置的?通过cas操作,将任务的结果设置到当前CompletableFuture实例的result属性中,如果任务正常结束,正常设置结果,如果任务抛出异常,把异常封装到一个Exception中

// 这是上一步中调用的// 分支一:设置返回值
final boolean completeValue(T t) {// 通过cas操作来更新当前CompletableFuture实例中的resultreturn UNSAFE.compareAndSwapObject(this, RESULT, null,(t == null) ? NIL : t);
}// 分支二:如果出现异常,设置异常返回值
final boolean completeThrowable(Throwable x) {// 也是通过cas操作,更新当前CompletableFuture实例中的result,只不过返回值是被包装到了AltResult中return UNSAFE.compareAndSwapObject(this, RESULT, null,encodeThrowable(x));
}
static AltResult encodeThrowable(Throwable x) {return new AltResult((x instanceof CompletionException) ? x :new CompletionException(x));
}
获取异步任务的返回值 get

如何获取异步任务的返回值?调用get方法或join方法,它们都会获取返回值。

整体流程:

// get方法
public T get() throws InterruptedException, ExecutionException {Object r;// 如果当前result实例等于null,调用waitingGet方法,阻塞地获取返回值,否则调用reportGet方法,// 上报返回值return reportGet((r = result) == null ? waitingGet(true) : r);
}// waitingGet方法:阻塞地获取返回值,将返回值写到result变量中
private Object waitingGet(boolean interruptible) {// 信号器,负责线程的阻塞和唤醒Signaller q = null;// 当前线程是否入队boolean queued = false;// 旋转次数int spins = -1;Object r;// 判断:result实例是否等于null,如果等于null,需要阻塞地获取返回值while ((r = result) == null) {// 先自旋:spins,它是自旋,自旋次数是CPU个数的8次方以上,因为自旋次数减减的// 之前,会生成一个随机数,生成的随机数大于等于0,自旋次数才会减1。如果自旋结束之后// 还没有得到结果,会进入阻塞队列。if (spins < 0)spins = SPINS;   // SPINS = (Runtime.getRuntime().availableProcessors() > 1 ? 1 << 8 : 0) 256次else if (spins > 0) {if (ThreadLocalRandom.nextSecondarySeed() >= 0)--spins;}else if (q == null)// 创建一个信号器,用于阻塞和唤醒线程q = new Signaller(interruptible, 0L, 0L);else if (!queued)// 如果信号器还没有入队,将信号器设置到当前CompletableFuture实例的stack属性中queued = tryPushStack(q);else if (interruptible && q.interruptControl < 0) {// 如果当前线程被打断,放弃阻塞,清理stack变量,返回nullq.thread = null;cleanStack();return null;}// 线程进入阻塞,底层调用LockSupport的park方法,等待异步任务执行完成之后唤醒当前线程else if (q.thread != null && result == null) {try {ForkJoinPool.managedBlock(q);} catch (InterruptedException ie) {q.interruptControl = -1;}}}// 如果信号器不等于nullif (q != null) {q.thread = null;if (q.interruptControl < 0) {if (interruptible)r = null; // report interruptionelseThread.currentThread().interrupt();}}// 触发后续任务postComplete();// 返回异步任务的结果,result的变量是由其它线程设置的,当前线程只需要阻塞地获取它。return r;
}

1、信号器入栈的方法:

// 将信号器设置到CompletableFuture的stack属性中
final boolean tryPushStack(Completion c) { // c是信号器实例Completion h = stack;  // stack,当前CompletableFuture实例的stack属性/* lazySetNext方法加下面的cas操作,相当于将信号器入栈,信号器链接到头结点并且成为新的头结点 */lazySetNext(c, h);return UNSAFE.compareAndSwapObject(this, STACK, h, c); 
}static void lazySetNext(Completion c, Completion next) {UNSAFE.putOrderedObject(c, NEXT, next);  // Completion的next属性指向下一个Completion,它们之间构成单向链表
}

2、阻塞当前线程的方法:

public static void managedBlock(ManagedBlocker blocker) throws InterruptedException {ForkJoinPool p;ForkJoinWorkerThread wt;Thread t = Thread.currentThread();if ((t instanceof ForkJoinWorkerThread) &&// 省略代码}else {// 阻塞当前线程。isReleasebale判断阻塞是否被释放,如果没有,继续进入阻塞状态do {} while (!blocker.isReleasable() &&!blocker.block());}
}// 阻塞当前线程,线程唤醒后,判断阻塞是否可以释放
public boolean block() {if (isReleasable())return true;else if (deadline == 0L)LockSupport.park(this);else if (nanos > 0L)LockSupport.parkNanos(this, nanos);return isReleasable();
}// 阻塞是否可以释放:信号器中的线程实例为null、或者线程被打断、或者超过指定时间,都算阻塞结束
public boolean isReleasable() {// 信号器中的线程实例为nullif (thread == null)return true;// 当前线程被打断if (Thread.interrupted()) {int i = interruptControl;interruptControl = -1;if (i > 0)return true;}// 过了超时时间if (deadline != 0L &&(nanos <= 0L || (nanos = deadline - System.nanoTime()) <= 0L)) {thread = null;return true;}return false;
}

3、上报异步任务的返回值:

// reportGet方法,处理异步任务的返回值
private static <T> T reportGet(Object r)throws InterruptedException, ExecutionException {// 如果返回值为空if (r == null) // by convention below, null means interruptedthrow new InterruptedException();// 如果返回一个异常实例if (r instanceof AltResult) {Throwable x, cause;if ((x = ((AltResult)r).ex) == null)return null;if (x instanceof CancellationException)throw (CancellationException)x;if ((x instanceof CompletionException) &&(cause = x.getCause()) != null)x = cause;throw new ExecutionException(x);}// 如果返回一个正常结果,将结果进行泛型转换,返回给调用者@SuppressWarnings("unchecked") T t = (T) r;return t;
}

获取返回值的时候,get方法中,会先判断有没有返回值,如果没有,自旋,自旋次数是CPU个数的8次方,如果还没有返回值,进入阻塞队列。

4、线程阻塞后在什么地方被唤醒?在postComplete方法中

// 参考异步任务的执行过程,执行完之后,会调用CompletableFuture的postComplete方法,
// 触发STACK属性中存储的下一个任务
final void postComplete() {CompletableFuture<?> f = this; Completion h;while ((h = f.stack) != null ||  // 判断stack属性中是否有下一个任务(f != this && (h = (f = this).stack) != null)) {CompletableFuture<?> d; Completion t;if (f.casStack(h, t = h.next)) {  // 下一个任务出栈,现在STACK属性中存储下下一个任务if (t != null) {  // 下下一个任务不为nullif (f != this) {pushStack(h);continue;}h.next = null;    // detach}f = (d = h.tryFire(NESTED)) == null ? this : d;  // 触发下一个任务的执行}}
}// 下一个任务出栈的方法
final boolean casStack(Completion cmp, Completion val) {return UNSAFE.compareAndSwapObject(this, STACK, cmp, val);
}// 下一个任务的执行:在这里下一个任务是信号器 Signaller
final CompletableFuture<?> tryFire(int ignore) {Thread w; // no need to atomically claimif ((w = thread) != null) {thread = null;LockSupport.unpark(w);  // 它会唤醒信号器中存储的线程}return null;
}

一元依赖

这里以thenApply为例。

整体流程:

private <V> CompletableFuture<V> uniApplyStage(Executor e, Function<? super T,? extends V> f) {if (f == null) throw new NullPointerException();CompletableFuture<V> d =  new CompletableFuture<V>();// 第一步:执行异步任务,如果当前任务可以直接执行的话,就直接执行if (e != null || !d.uniApply(this, f, null)) {  // 参数this是上一个异步任务// 第二步:当前任务无法直接执行,创建UniApply实例UniApply<T,V> c = new UniApply<T,V>(e, d, this, f);// 第三步:将当前实例推入栈中,表示上一个异步任务执行完成后触发push(c);  // this是上一个异步任务,所以push方法是由上一个异步任务的实例调用的// 尝试触发异步任务c.tryFire(SYNC);}return d;
}

1、异步任务的执行逻辑

// uniApply方法:执行异步任务,参数a是上一个异步任务,需要判断上一个异步任务有没有执行完成并且获取它的执行结果
final <S> boolean uniApply(CompletableFuture<S> a,Function<? super S,? extends T> f,UniApply<S,T> c) {  // 参数c是当前异步任务实例Object r; Throwable x;// 如果上一个异步任务没有结果,返回falseif (a == null || (r = a.result) == null || f == null)return false;// 1、判断当前异步任务是否可以执行:如果当前异步任务没有结果tryComplete: if (result == null) {// 如果上一个异步任务是异常结束的if (r instanceof AltResult) {if ((x = ((AltResult)r).ex) != null) {completeThrowable(x, r);break tryComplete;  // 退出循环,不执行当前异步任务}r = null;}try {// 2、确保异步任务只会被执行一次if (c != null && !c.claim())return false;// 3、执行异步任务,并且上报结果@SuppressWarnings("unchecked") S s = (S) r;completeValue(f.apply(s));} catch (Throwable ex) {completeThrowable(ex);}}return true;
}

2、如果异步任务不可以直接执行,需要把异步任务push到上一个异步任务的STACK属性中

// 将新的异步任务推入栈中,参数c是当前异步任务
final void push(UniCompletion<?,?> c) {if (c != null) {// 如果当前异步任务没有结束,将异步任务推入栈中,这里是推入栈顶while (result == null && !tryPushStack(c))// 失败时清除,将新异步任务的next值设为nulllazySetNext(c, null); // clear on failure}
}

3、再次尝试触发异步任务

// 尝试触发异步任务
final CompletableFuture<V> tryFire(int mode) {CompletableFuture<V> d; CompletableFuture<T> a;if ((d = dep) == null ||// 执行异步任务!d.uniApply(a = src, fn, mode > 0 ? null : this))return null;// 执行成功后,属性置空dep = null; src = null; fn = null;// 如果当前异步任务执行成功,触发后续任务return d.postFire(a, mode);
}

4、触发下一个异步任务

// 触发下一个异步任务,这里会触发前一个异步任务的后序任务和当前异步任务的后序任务
final CompletableFuture<T> postFire(CompletableFuture<?> a, int mode) {// 会判断前一个异步任务和当前异步任务有没有下一个任务。// a是前一个任务if (a != null && a.stack != null) {if (a.result == null)a.cleanStack();else if (mode >= 0)a.postComplete();}// this是当前任务if (result != null && stack != null) {if (mode < 0)return this;elsepostComplete();}return null;
}// postComplete
final void postComplete() {CompletableFuture<?> f = this; Completion h;// 如果当前异步任务有下一个任务while ((h = f.stack) != null ||(f != this && (h = (f = this).stack) != null)) {CompletableFuture<?> d; Completion t;// 使用下下一个异步任务,代替下一个任务,赋值给stack变量if (f.casStack(h, t = h.next)) {if (t != null) {if (f != this) {pushStack(h);continue;}h.next = null;    // detach}// 执行下一个异步任务,然后接收它的返回值f = (d = h.tryFire(NESTED)) == null ? this : d;}}
}
如何确保异步任务只执行一次

claim方法

final boolean claim() {Executor e = executor;// 这个方法来自ForkJoinTask,用cas算法来操作任务实例中的status字段,保证任务只会被执行一次if (compareAndSetForkJoinTaskTag((short)0, (short)1)) {// 如果当前异步任务没有可用的线程池,返回true,由当前线程执行if (e == null)return true;// 把异步任务提交给线程池执行executor = null; // disablee.execute(this);}return false;
}

二元依赖

这里以thenCombine方法为例

整体流程:

private <U,V> CompletableFuture<V> biApplyStage(Executor e, CompletionStage<U> o,BiFunction<? super T,? super U,? extends V> f) {CompletableFuture<U> b;if (f == null || (b = o.toCompletableFuture()) == null)throw new NullPointerException();CompletableFuture<V> d = new CompletableFuture<V>();// biApply方法中的参数this、b,是当前异步任务依赖的前两个异步任务if (e != null || !d.biApply(this, b, f, null)) {BiApply<T,U,V> c = new BiApply<T,U,V>(e, d, this, b, f);bipush(b, c);  // this和b是依赖,c是要被触发的异步任务c.tryFire(SYNC);}return d;
}

执行逻辑:

final <R,S> boolean biApply(CompletableFuture<R> a,CompletableFuture<S> b,BiFunction<? super R,? super S,? extends T> f,BiApply<R,S,T> c) {Object r, s; Throwable x;// 关键逻辑,判断当前异步任务所依赖的两个异步任务是否完成,如果没有,退出if (a == null || (r = a.result) == null ||b == null || (s = b.result) == null || f == null)return false;tryComplete: if (result == null) {if (r instanceof AltResult) {if ((x = ((AltResult)r).ex) != null) {completeThrowable(x, r);break tryComplete;}r = null;}if (s instanceof AltResult) {if ((x = ((AltResult)s).ex) != null) {completeThrowable(x, s);break tryComplete;}s = null;}try {if (c != null && !c.claim())return false;@SuppressWarnings("unchecked") R rr = (R) r;@SuppressWarnings("unchecked") S ss = (S) s;completeValue(f.apply(rr, ss));} catch (Throwable ex) {completeThrowable(ex);}}return true;
}

总结

CompletableFuture存储了计算逻辑,Completion存储了计算过程中需要用到的数据。

两个有依赖关系的异步任务是如何被编排在一起的?上一个异步任务结束之后如何触发下一个异步任务?通过CompletableFuture的stack属性,当前异步任务执行完之后,会获取stack属性中的值,这个值就是下一个需要计算的异步任务

参考:
https://tech.meituan.com/2022/05/12/principles-and-practices-of-completablefuture.html
https://www.cnblogs.com/Createsequence/p/16963895.html
https://javaguide.cn/java/concurrent/completablefuture-intro.html

相关文章:

多线程-CompletableFuture

简介 CompletableFuture&#xff1a;异步任务编排工具。java 8中引入的一个类&#xff0c;位于juc包下&#xff0c;是Future的增强版。它可以让用户更好地构建和组合异步任务&#xff0c;避免回调地狱。 在CompletableFuture中&#xff0c;如果用户没有指定执行异步任务时的线…...

常用限流算法解析与实现

‌一、固定窗口计数器法‌ ‌原理‌&#xff1a;在固定时间窗口&#xff08;如1秒&#xff09;内统计请求次数&#xff0c;超过阈值则触发限流。 ‌Java实现‌&#xff1a; public class FixedWindowCounter { private static final long WINDOW_MS 1000; // 1秒窗口 priv…...

Swift系列02-Swift 数据类型系统与内存模型

Swift 是一门现代的、安全的编程语言&#xff0c;其类型系统和内存模型设计对性能和安全性有着重要影响。本文将深入探讨 Swift 的数据类型系统与内存模型&#xff0c;帮助你更好地理解并利用这些特性来优化你的 iOS 应用。本文主要包含&#xff1a; 值类型和引用类型&#xf…...

如何不重启,生效windows环境变量

场景 使用php 进行composer 时&#xff0c;composer 要求php7.2以上&#xff0c;我常用的是7.1&#xff0c;不想来回修改&#xff0c;还是重启电脑 临时修改 打印当前环境变量 echo %PATH%临时修改当前环境变量&#xff08;如果需要指定的值&#xff0c;可将全部复制出来&a…...

Ubuntu20.04本地配置IsaacLab 4.2.0的G1训练环境(二):训练与推理

Ubuntu20.04本地配置IsaacLab4 4.2.0的G1训练环境&#xff08;二&#xff09;&#xff1a;训练与推理 训练推理 写在前面&#xff0c;本文档的实现需要IsaacLab的成功安装&#xff0c;可参考&#xff08;一&#xff09;。 训练 在IsaacLab目录下&#xff0c;isaaclab的conda虚…...

设计模式说明

23种设计模式说明 以下是常见的 23 种设计模式 分类及其核心思想、应用场景和简单代码示例&#xff0c;帮助你在实际开发中灵活运用&#xff1a; 一、创建型模式&#xff08;5种&#xff09; 解决对象创建问题&#xff0c;降低对象耦合。 1. 单例模式&#xff08;Singleton&…...

K8s 1.27.1 实战系列(四)验证集群及应用部署测试

一、验证集群可用性 1、检查节点 kubectl get nodes ------------------------------------------------------ NAME STATUS ROLES AGE VERSION k8s-master Ready control-plane 3h48m v1.27.1 k8s-node1 Ready <none> …...

Artec Leo+Ray II 三维扫描仪成功为VR展数字化30吨重设备-沪敖3D

挑战&#xff1a;在贸易展上展示重达30吨的机械设备&#xff0c;同时克服设备搬运和展示的难题&#xff0c;减轻物流负担。。 解决方案&#xff1a;Artec Leo、Artec Ray II、Artec Studio、Blender、Unity、Microsoft HoloLens、HTC VIVE PRO 效果&#xff1a;在虚拟展厅中&am…...

Redis 各数据类型使用场景详解

1. 字符串&#xff08;String&#xff09; 场景 1&#xff1a;计数器&#xff08;如文章阅读量&#xff09; 问题&#xff1a; 高并发下对同一数值进行增减操作时&#xff0c;需保证原子性&#xff0c;避免竞态条件导致数据不一致。 频繁读写可能成为性能瓶颈。 解决方案&a…...

spark写数据库用连接池找不到driver类

最近遇到一个很离谱的bug&#xff0c;在写spark代码把数据写到mysql的时候考虑到连接的开销&#xff0c;所以用了HikariCP连接池&#xff0c;但是无语的是程序执行死活加载不到mysql的Driver类&#xff0c;但是解压了jar看到mysql-conn包就在lib下面&#xff0c;版本也是5.x的没…...

上传文件到对象存储是选择前端还是后端

对于云上对象存储的上传方式选择&#xff08;前端直传或后端代理上传&#xff09;&#xff0c;需综合考虑安全性、性能、成本、业务需求等因素。 1. 推荐前端直传的场景 适用条件&#xff1a; 大文件上传&#xff08;如视频、大型数据集&#xff09;高并发场景&#xff08;如…...

NanoMQ ds笔记250306

NanoMQ多版本下载地址 https://www.emqx.com/zh/downloads/nanomq NanoMQ官方文档 https://nanomq.io/docs/zh/latest/ NanoMQ 是一个专为物联网边缘计算设计的轻量级、高性能 MQTT 消息代理&#xff08;Message Broker&#xff09;&#xff0c;由中国的开源物联网公司 EMQ 开…...

sqlmap:从基础用法到漏洞利用实战

1. sqlmap基础认知 sqlmap是一款开源的渗透测试工具&#xff0c;能自动检测和利用SQL注入漏洞&#xff0c;支持MySQL、Oracle、PostgreSQL等多种数据库管理系统。其设计旨在简化SQL注入检测流程&#xff0c;助力安全人员在复杂网络环境中快速定位与评估漏洞风险。它通过发送精…...

DFS学习笔记

题目描述 X 国王有一个地宫宝库。是 nm 个格子的矩阵。每个格子放一件宝贝。每个宝贝贴着价值标签。 地宫的入口在左上角&#xff0c;出口在右下角。 小明被带到地宫的入口&#xff0c;国王要求他只能向右或向下行走。 走过某个格子时&#xff0c;如果那个格子中的宝贝价值…...

C++ STL string容器全解析

一、引言 在 C 编程的广阔领域中&#xff0c;字符串处理是一项极为基础且频繁的操作。从简单的文本解析&#xff0c;到复杂的文件读取与处理&#xff0c;字符串几乎无处不在。而 C 中的 string 容器&#xff0c;就像是一把瑞士军刀&#xff0c;为我们处理字符串提供了强大而便…...

React基础之项目创建

项目创建 create-react-app 项目名(小写) 运行 pnpm run start 在React中&#xff0c;使用的语法格式是jsx&#xff0c;也就是js与html相结合的形式 import logo from ./logo.svg; import ./App.css; function App() { return ( <div className"App"> <head…...

迷你世界脚本道具接口:Item

道具接口&#xff1a;Item 彼得兔 更新时间: 2023-04-26 10:26:18 继承自 Actor 具体函数名及描述如下: 序号 函数名 函数描述 1 getItemName(...) 获取道具名称 2 getItemId(...) 获取actor对应的道具ID&#xff0c;如球类等 3 getDropItemNum(...) …...

Unity摄像机跟随物体

功能描述 实现摄像机跟随物体&#xff0c;并使物体始终保持在画面中心位置。 实现步骤 创建脚本&#xff1a;在Unity中创建一个新的C#脚本&#xff0c;命名为CameraFollow。 代码如下&#xff1a; using UnityEngine;public class CameraFollow : MonoBehaviour {public Tran…...

计算机毕业设计SpringBoot+Vue.js青年公寓服务平台(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…...

vue实现日历签到效果

在工作任务进行时&#xff0c;有一个签到日历的功能需求要实现&#xff0c;经过文档查询和样式优化实现了需求&#xff0c;在此记录一下。 技术背景&#xff1a;vue2vant(样式控件&#xff09; less 一个公共样式文件 html实现部分&#xff1a; <div class"calenderB…...

(十 八)趣学设计模式 之 观察者模式!

目录 一、 啥是观察者模式&#xff1f;二、 为什么要用观察者模式&#xff1f;三、 观察者模式的实现方式四、 观察者模式的优缺点五、 观察者模式的应用场景六、 总结 &#x1f31f;我的其他文章也讲解的比较有趣&#x1f601;&#xff0c;如果喜欢博主的讲解方式&#xff0c;…...

笔记:在Git中.gitmodules文件的功能和作用和如何使用

一、目的&#xff1a;简单介绍下在Git中.gitmodules文件的功能和作用已经 .gitmodules 文件是 Git 子模块&#xff08;submodule&#xff09;功能的一部分&#xff0c;用于管理和配置子模块。子模块允许一个 Git 仓库包含另一个 Git 仓库作为其子目录&#xff0c;这对于管理依赖…...

Swift 常量

Swift 常量 引言 Swift 是一种由苹果公司开发的编程语言,主要用于 iOS、macOS、watchOS 和 tvOS 等平台的应用开发。在 Swift 中,常量是一种不可变的变量,它用于存储固定不变的值。了解和使用常量是 Swift 编程的基础,本文将详细介绍 Swift 常量的概念、类型、声明以及使…...

Ubuntu20.04双系统安装及软件安装(七):Anaconda3

Ubuntu20.04双系统安装及软件安装&#xff08;七&#xff09;&#xff1a;Anaconda3 打开Anaconda官网&#xff0c;在右侧处填写邮箱&#xff08;要真实有效&#xff01;&#xff09;&#xff0c;然后Submit。会出现如图示的Success界面。 进入填写的邮箱&#xff0c;有一封Ana…...

Google AI概览升级,AI模式全新登场!

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…...

【智能体架构:Agent】LangChain智能体类型ReAct、Self-ASK的区别

1. 什么是智能体 将大语言模型作为一个推理引擎。给定一个任务&#xff0c; 智能体自动生成完成任务所需步骤&#xff0c; 执行相应动作&#xff08;例如选择并调用工具&#xff09;&#xff0c; 直到任务完成。 2. 先定义工具&#xff1a;Tools 可以是一个函数或三方 API也…...

nginx 配置403页面(已亲测)

问题&#xff1a;GET请求访问漏洞url即可看到泄露的内网ip 解决方式&#xff1a; 1.配置nginx 不显示真实Ip 2.限制接口只能是POST请求 具体配置&#xff1a; 编写一个403.html 在nginx的配置文件中&#xff0c;配置location参数&#xff1a; location /api/validationCode…...

安卓基础组件Looper - 02 native层面的剖析

文章目录 native使用使用总结创建Looper构造函数创建(不推荐)使用举例源代码 Looper::prepare 获取Looper可忽略初始化Looper主动休眠 pollAll主动唤醒 wake 发送消息 sendMessage轮询消息 native使用 Android Native Looper 机制 - 掘金 (juejin.cn) /system/core/libutils/…...

nodejs关于后端服务开发的探究

前提 在当前的环境中关于web server的主流开发基本上都是java、php之类的&#xff0c;其中java spring系列基本上占了大头&#xff0c;而python之流也在奋起直追&#xff0c;但别忘了nodejs也是可以做这个服务的&#xff0c;只是位置有点尴尬&#xff0c;现在就来探究下nodejs…...

QTday4

1:是进度条通过线程自己动起来 mythread.h #ifndef MYTHREAD_H #define MYTHREAD_H #include <QThread>class mythread : public QThread {Q_OBJECT public:mythread(QObject* parent nullptr); protected:virtual void run() override; private: signals:virtual voi…...

服务器时间同步

方法一 [rootbogon hwh-ansible]# cat time-sync.sh #!/bin/bash # NTP 服务器信息 NTP_SERVER"192.168.42.12" PASSWORD"123456" # 多个 IP 地址 HOSTS("192.168.42.8" "192.168.42.9" "192.168.42.10" "192.168.42…...

蓝桥杯备赛日记【day1】(c++赛道)

一、裁纸刀问题&#xff08;2022、规律、思维、省赛&#xff09; 解法思路&#xff1a; 参考题目给出的例子发现。不管要裁剪多少次。最外围的四次是固定的。然后通过观察发现&#xff0c;我们的行的裁剪次数为&#xff08;m-1&#xff09; 次&#xff0c;而每行都需要裁剪列数…...

DeepSeek大模型 —— 全维度技术解析

DeepSeek大模型 —— 全维度技术解析 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家&#xff01;点我试试&#xff01;&#xff01; 文章目录 DeepSeek大模型 —— 全维度技术解析一、模型架构全景解析1.1…...

嵌入式开发:傅里叶变换(5):基于STM32,实现CMSIS中的DSP库

目录 步骤 1&#xff1a;准备工作 步骤 2&#xff1a;创建 Keil 项目&#xff0c;并配置工程 步骤 3&#xff1a;在MDK工程上添加 CMSIS-DSP 库 步骤 5&#xff1a;编写代码 步骤 6&#xff1a;配置时钟和优化 步骤 7&#xff1a;调试与验证 步骤 8&#xff1a;优化和调…...

Ubuntu 24.04 配置ODBC连接ORACLE 11G数据库

1. 安装必要工具和驱动 1.1 安装unixODBC和依赖库 # apt update # apt install unixodbc unixodbc-dev libaio1 执行失败&#xff0c;报错 libaio1包找不到&#xff0c;先跳过&#xff0c;安装其他两个。 # apt install unixodbc unixodbc-dev 安装成功 1.2 下载Oracle…...

upload-labs靶场 1-21通关

目录 1.Pass-01 前端绕过 分析 解题 2.Pass-02 服务器端检测--修改IMME 分析 解题 3.Pass-03 黑名单绕过 分析 解题 4.Pass-04 .htaccess绕过 分析 解题 5.Pass-05 . .绕过和.user.ini绕过 分析 解题 6.Pass-06 大小写绕过 分析 解题 7.Pass-07 空格绕过 分…...

Docker新手入门(持续更新中)

一、定义 快速构建、运行、管理应用的工具。 Docker可以帮助我们下载应用镜像&#xff0c;创建并运行镜像的容器&#xff0c;从而快速部署应用。 所谓镜像&#xff0c;就是将应用所需的函数库、依赖、配置等应用一起打包得到的。 所谓容器&#xff0c;为每个镜像的应用进程创建…...

c语言笔记 指针篇(上)

1.指针 在计算的存储器中有很多的存储单元&#xff0c;我们的操作系统把这些存储单元以字节为单位进行编号&#xff0c;也就是每个存储单元&#xff08;字节&#xff09;&#xff0c;都有编码。这些编码在我们内存中就称为地址。一个字节有八位&#xff0c;位是存储信息的最小单…...

要查看 SQLite 数据库中的所有表,可以通过查询 SQLite 的系统表 sqlite_master

要查看 SQLite 数据库中的所有表&#xff0c;可以查询 SQLite 的系统表 sqlite_master。 每个 SQLite 数据库都包含一个名为 sqlite_master 的系统表。该表定义了数据库的模式&#xff0c;存储了数据库中所有表、索引、视图和触发器等对象的信息。 通过查询 sqlite_master&am…...

C#释放内存空间的方法

目录 前言释放 C# 对象内存的六种方法1、手动释放内存空间2、使用 Using 语句3、使用 垃圾回收器4、GC.Collect() 方法5、GC.WaitForPendingFinalizers() 方法6、WeakReference 类 注意 前言 当不再需要对象时释放内存空间对于防止内存泄漏和提高应用程序性能至关重要。C# 提供…...

mapbox基础,使用点类型geojson加载symbol符号图层,用于标注文字

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性1.3 ☘️symbol符号图层样式二、🍀使用点类型…...

Java数组详解/从JVM理解数组/数组反转/随机排名/数组在计算机如何存储

本文详细讲解了数组的定义、数组的访问方法、数组的遍历、静态数组和动态数组、以及数组中的自动类型转换、引用类型指向数组的地址、以及从JVM理解数组、空指针异常、数组反转、随机排名的案例。 数组是存放在连续内存空间上的相同类型数据的集合。 数组可以方便的通过下标索…...

网络安全wireshark题目

一、填空题&#xff1a; 网络安全的目标是在计算机网络的信息传输、存储与处理的整个过程中&#xff0c;提高 物理逻辑上 的防护、监控、反应恢复和 对抗 的能力。SSL协议是在网络传输过程中&#xff0c;提供通信双方网络信息 保密性 和 可靠性 。TCP/IP网络安全管理…...

TomcatServlet

https://www.bilibili.com/video/BV1UN411x7xe tomcat tomcat 架构图&#xff0c;与 jre&#xff0c;应用程序之前的关系 安装使用 tomcat 10 开始&#xff0c;api 从 javax.* 转为使用 jakarta.*&#xff0c;需要至少使用 jdk 11 cmd 中默认 gbk 编码&#xff0c;解决控制…...

Seurat - Guided Clustering Tutorial官方文档学习及复现

由于本人没有使用过Seurat4.0&#xff0c;而是直接使用的最新版。所以本文都是基于Seurat5.2.0&#xff08;截止2025/3/6&#xff09;来进行撰写。 参考的官方教程来进行学习&#xff08;上图中的 Guided tutorial-2.700 PBMCs&#xff09;&#xff0c;肯定没有官方文档那么全面…...

Python数据分析面试题及参考答案

目录 处理 DataFrame 中多列缺失值的 5 种方法 批量替换指定列中的异常值为中位数 使用正则表达式清洗电话号码格式 合并两个存在部分重叠列的 DataFrame 将非结构化 JSON 日志转换为结构化表格 处理日期列中的多种非标准格式(如 "2023 年 12 月 / 05 日") 识…...

极狐GitLab 正式发布安全版本17.9.1、17.8.4、17.7.6

本分分享极狐GitLab 补丁版本 17.9.1、17.8.4、17.7.6 的详细内容。这几个版本包含重要的缺陷和安全修复代码&#xff0c;我们强烈建议所有私有化部署用户应该立即升级到上述的某一个版本。对于极狐GitLab SaaS&#xff0c;技术团队已经进行了升级&#xff0c;无需用户采取任何…...

【JavaSE-7】方法的使用

1、方法的概念和使用 1.1、什么是方法 方法&#xff08;method&#xff09;是程序中最小的执行单元&#xff0c;类似于 C语言中的函数&#xff0c;方法存在的意义&#xff1a; 是能够模块化的组织代码(当代码规模比较复杂的时候).做到代码被重复使用, 一份代码可以在多个位置…...

阿里推出全新推理模型(因果语言模型),仅1/20参数媲美DeepSeek R1

阿里Qwen 团队正式发布了他们最新的研究成果——QwQ-32B大语言模型&#xff01;这款模型不仅名字萌萌哒(QwQ)&#xff0c;实力更是不容小觑&#xff01;&#x1f60e; QwQ-32B 已在 Hugging Face 和 ModelScope 开源&#xff0c;采用了 Apache 2.0 开源协议。大家可通过 Qwen C…...

C语言笔记(通讯录)

目录 1.通讯录的架构 2.通讯录的功能 3.实现静态通讯录的功能步骤 3.1.创建通讯录数组 3.2.显示功能菜单 3.3.初始化通讯录 3.4.添加联系人的信息 3.5.显示联系人的信息 3.6.查找某个人的信息 3.7.删除某一个联系人信息 3.8.修改某一联系人的信息 3.9.按名字对联系…...