四十三、异步编程之CompletableFuture

91 阅读12分钟

CompletableFuture

CompletableFuture概述

简单的异步编程,使用Runnable、Callable、Thread、FutureTask、ThreadPoolExecutor可以实现

Runnable+Thread:异步执行一个任务

Callable+FutureTask+Thread:异步执行一个任务,还可以获得返回结果

但是对于一些复杂的逻辑,例如三个任务A、B、C,任务B和任务C需要等任务A执行完成返回结果后,才能执行。这种场景需要写处理任务执行逻辑的代码,CompletableFuture就适合处理任务的执行逻辑。

CompletableFuture通过对多个任务进行编排,让它们在执行时按照业务顺序执行。

CompletableFuture应用

CompletableFuture是函数式编程,任务作为入参。

对于CompletableFuture来说核心有四种任务,分别是Runnable、Supplier、Consumer、Function

  • Runnable:既没有入参,也没有返回结果的任务
  • Supplier:没有入参,但是有返回结果的任务
  • Consumer:有入参,但是没有返回结果的任务
  • Function:既有入参,又有返回结果的任务

CompletableFuture基本方法

supplyAsync方法

supplyAsync方法是Supplier类型,必须有返回值

public class Test {

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1);

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("方法开始执行");
            return "任务执行结束";
        }, executorService);

        String re1 = future.join();
        System.out.println("返回结果:" + re1);
        try {
            String re2 = future.get();
            System.out.println("返回结果:" + re2);
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        executorService.shutdown();
    }
}

要点1:

supplyAsync方法第一个参数是要执行的任务,第二个参数是执行任务的线程池;其实supplyAsync还有一个重载的方法,只需要传入任务,默认使用的是ForkJoinPool。ForkJoinPool线程池中都是守护线程,如果main线程停止了,可能任务还没有执行。

要点2:

可以通过使用get和join方法获取任务的执行结果。使用get方法需要捕获异常,join方法内部已经处理了异常,所以不需要额外处理。

分析点1:

如果是与join和get方法一起使用,supplyAsync使用ForkJoinPool线程池也没有关系,关键是main线程去执行获取结果的方法,这样main线程在任务执行完前就不会停止。

总结:

supplyAsync方法执行的任务不需要入参,适合作为CompletableFuture的第一个任务。

runAsync方法

runAsync方法使用的是Runnable任务,没有入参也没有返回结果

public class Test {

    public static void main(String[] args) {

        CompletableFuture future = CompletableFuture.runAsync(() -> {
            System.out.println("任务开始执行");
            System.out.println("任务执行结束");
        });

        future.join();
    }
}

要点1:

runAync方法传入的任务不会返回结果,所以使用join或者get方法也得不到结果

thenApply方法

public class Test {

    public static void main(String[] args) {

//        CompletableFuture taskA = CompletableFuture.supplyAsync(() -> {
//            System.out.println("任务开始执行");
//            System.out.println("执行任务的线程:" + Thread.currentThread().getName());
//            return "任务A";
//        });
//
//        CompletableFuture taskB = taskA.thenApply(result -> {
//            System.out.println("开始执行任务B");
//            System.out.println("执行任务的线程:" + Thread.currentThread().getName());
//            System.out.println("任务A的返回结果:" + result);
//            return "任务B";
//        });

        // -------------------更优雅的写法--------------------------
        CompletableFuture taskB = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务开始执行");
            System.out.println("执行任务的线程:" + Thread.currentThread().getName());
            return "任务A";
        }).thenApply(result -> {
            System.out.println("开始执行任务B");
            System.out.println("执行任务的线程:" + Thread.currentThread().getName());
            System.out.println("任务A的返回结果:" + result);
            return "任务B";
        });

        System.out.println("任务B的返回结果:" + taskB.join());
    }
}

要点1:

前置任务执行完成后,调用thenApply方法,拿到前置任务的执行结果,执行后置任务。

要点2:

除了thenApply方法,还有thenApplyAsync方法有相同的效果。thenApplyAsync可以传线程池,也可以不传。传线程池,就用指定线程池执行任务;不传线程池,可能会用与前置任务相同的线程去执行任务,也可能换一个线程去执行任务。

分析点1:

thenApply方法可以编排两个任务,是体现CompletableFuture核心功能的基本方法。

thenAccept方法

public class Test {

    public static void main(String[] args) throws IOException {
        CompletableFuture.supplyAsync(() -> {
            System.out.println("任务开始执行");
            System.out.println("执行任务的线程:" + Thread.currentThread().getName());
            return "任务A";
        }).thenAccept(result -> {
            System.out.println("开始执行任务B");
            System.out.println("执行任务的线程:" + Thread.currentThread().getName());
            System.out.println("任务A的返回结果:" + result);
        });

        System.in.read();
    }
}

要点1:

thenAccept方法的作用与thenApply方法相似,不过thenAccept接收的是有入参,但是没有返回结果的任务

要点2:

thenAccept方法同样也有功能相同的thenAcceptAsync方法,区别也是可以传线程池

要点3:

虽然thenAccept方法不需要入参,但是两个任务还是有执行的顺序。前置任务先执行,后置任务后执行

thenRun方法

public class Test {

    public static void main(String[] args) throws IOException {
        CompletableFuture.runAsync(() -> {
            System.out.println("任务开始执行");
            System.out.println("执行任务的线程:" + Thread.currentThread().getName());
        }).thenRun(() -> {
            System.out.println("开始执行任务B");
            System.out.println("执行任务的线程:" + Thread.currentThread().getName());
        });

        System.in.read();
    }
}

要点1:

thenRun方法接收的是既没有入参,也没有返回结果的任务

要点2:

thenRun方法同样也有功能相同的thenRunAsync方法,区别也是可以传线程池

thenCombine方法

public class Test {

    public static void main(String[] args) {
        CompletableFuture<Integer> taskC = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务A执行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 1;
        }).thenCombine(CompletableFuture.supplyAsync(() -> {
            System.out.println("任务B执行");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 1;
        }), (reA, reB) -> {
            System.out.println("任务C执行");
            return reA + reB;
        });

        System.out.println(taskC.join());
    }
}

要点1:

thenCombine方法是编排三个任务,前两个任务同时执行,最后一个任务在前两个任务执行完成后再执行

要点2:

前两个任务需要有返回值,最后一个任务既要有入参,也要有返回值

要点3:

编排三个任务的方法,除了thenCombine,还有thenAcceptBoth和runAfterBoth

  • thenAcceptBoth:最后一个任务只有入参,没有返回值
  • runAfterBoth:最后一个任务既没有入参,也没有返回值

applyToEither方法

public class Test {
    public static void main(String[] args) {
        CompletableFuture<Integer> taskC = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务A执行");
            return 1;
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
            System.out.println("任务B执行");
            return 2;
        }), re -> {
            System.out.println("任务C执行");
            return re;
        });

        System.out.println(taskC.join());
    }
}

要点1:

applyToEither方法也是编排三个任务的方法,前两个任务同时执行,最后一个任务只有一个入参,取的是前两个任务中优先返回的结果

要点2:

acceptEither和runAfterEither方法与applyToEither一样,区别在于:

  • acceptEither:最后一个任务只有入参,没有返回值
  • runAfterEither:最后一个任务既没有入参,也没有返回值

exceptionally方法

public class Test {
    public static void main(String[] args) {
        CompletableFuture<Integer> taskC = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务A执行");
            return 1;
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
            System.out.println("任务B执行");
            int i = 1/0;
            return 2;
        }), re -> {
            System.out.println("任务C执行");
            return re;
        }).exceptionally(ex -> {
            System.out.println(ex.getMessage());
            return -1;
        });

        System.out.println(taskC.join());
    }
}

要点1:

exceptionally方法是处理任务执行过程中发生的异常,如果没有发生异常,exceptionally方法不会执行

要点2:

exceptionally方法的入参是异常,方法也是要有返回值的

要点3:

exceptionally方法可以放在任务编排最后,也可以放在中间

whenComplete方法

public class Test {
    public static void main(String[] args) throws IOException {
        CompletableFuture<Integer> taskC = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务A执行");
            return 1;
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
            System.out.println("任务B执行");
            int i = 1/0;
            return 2;
        }), re -> {
            System.out.println("任务C执行");
            return re;
        }).whenComplete((r, ex) -> {
            System.out.println(r);
            System.out.println(ex.getMessage());
        });

        System.in.read();
    }
}

要点1:

whenComplete方法与exceptionally方法的区别

  • whenComplete方法有两个入参,一个是任务返回的结果,一个是异常
  • whenComplete方法没有返回值
  • whenComplete方法在没有异常情况下也会执行
  • whenComplete方法会将异常抛出,不会捕获

要点2:

handle方法也是处理异常的方法,它与whenComplete的区别

  • handle方法有返回值
  • handle方法会将异常捕获,不会抛出

allOf方法

public class Test {
    public static void main(String[] args) throws IOException {
        CompletableFuture.allOf(
                CompletableFuture.runAsync(() -> {
                    System.out.println("任务A");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }),
                CompletableFuture.runAsync(() -> {
                    System.out.println("任务B");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                })
        ).thenRun(() -> {
            System.out.println("任务C");
        });

        System.in.read();
    }
}

要点1:

allOf方法的入参是多个任务,并且没有返回结果

要点2:

allOf方法是将多个任务同时执行,并且任务都执行完成后,才能执行allOf方法的后置任务

anyOf方法

public class Test {
    public static void main(String[] args) throws IOException {
        CompletableFuture<Integer> task = CompletableFuture.anyOf(
                CompletableFuture.supplyAsync(() -> {
                    System.out.println("任务A");
                    return 1;
                }),
                CompletableFuture.supplyAsync(() -> {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("任务B");
                    return 2;
                })
        ).thenApply(re -> {
            System.out.println("任务C");
            return 3;
        });

        System.out.println(task.join());
        System.in.read();
    }
}

要点1:

anyOf方法的入参是多个任务,并且有返回结果

要点2:

anyOf方法是多个任务同时执行,只要有一个任务返回结果,就开始执行anyOf的后置任务

CompletableFuture源码

基本属性

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
	// 任务返回结果
    volatile Object result;   
    volatile Completion stack;  
}

基本方法

runAsync方法

public static CompletableFuture<Void> runAsync(Runnable runnable) {
	// asyncPool:执行任务的线程池
	// runnable:任务
	return asyncRunStage(asyncPool, runnable);
}

static CompletableFuture<Void> asyncRunStage(Executor e, Runnable f) {
	// 任务为null,抛异常
	if (f == null) throw new NullPointerException();
	// 声明CompletableFuture对象
	CompletableFuture<Void> d = new CompletableFuture<Void>();
	// 将CompletableFuture对象和任务封装成AsyncRun对象,交给线程池执行
	e.execute(new AsyncRun(d, f));
	return d;
}
static final class AsyncRun extends ForkJoinTask<Void>
		implements Runnable, AsynchronousCompletionTask {
	// dep:CompletableFuture对象
	// fn:将要执行的任务
	CompletableFuture<Void> dep; Runnable fn;
	AsyncRun(CompletableFuture<Void> dep, Runnable fn) {
		// 成员变量赋值
		this.dep = dep; this.fn = fn;
	}

	public final Void getRawResult() { return null; }
	public final void setRawResult(Void v) {}
	public final boolean exec() { run(); return true; }

	public void run() {
		CompletableFuture<Void> d; Runnable f;
		if ((d = dep) != null && (f = fn) != null) {
			dep = null; fn = null;
			// 判断任务是否已经执行过
			if (d.result == null) {
				try {
					// 执行任务
					f.run();
					// 任务执行完成
					// Runnable类型的任务执行完成后,会给CompletableFuture对象的result赋值NIL
					d.completeNull();
				} catch (Throwable ex) {
					// 执行任务失败
					// 封装失败结果
					d.completeThrowable(ex);
				}
			}
			// 任务编排需要调用的方法
			d.postComplete();
		}
	}
}
final boolean completeNull() {
	return UNSAFE.compareAndSwapObject(this, RESULT, null,
									   NIL);
}

final boolean completeThrowable(Throwable x) {
	return UNSAFE.compareAndSwapObject(this, RESULT, null,
									   encodeThrowable(x));
}

步骤:

  1. 判断任务是否为null,如果为null,抛出异常
  2. 声明CompletableFuture对象,将此对象与任务封装成AsyncRun对象,交给线程池执行
  3. 线程池执行AsyncRun对象的run方法
  4. 判断CompletableFuture对象的result属性是否为null,如果不为null,说明任务已经执行过了;如果为null,说明任务还未执行
  5. 任务还未执行,开始执行任务
  6. 任务正常结束,将CompletableFuture的result属性赋值为NIL
  7. 任务异常结束,将CompletableFuture的result属性赋值为异常信息
  8. 执行postComplete方法

任务编排后的存储

首先如果要在前置任务处理后,执行后置任务的话,有两种情况:

  • 前置任务没有执行结束,后置任务已经编排完成,那么后置任务需要放到stack栈结构中存储
  • 前置任务执行结束,后置任务才编排完成,后置任务可以直接执行,不需要放到stack栈中了

如果单独采用thenAsync方法在一个任务后面指定多个任务,CompletableFuture无法保证具体的执行顺序,而影响执行顺序的是前置任务的执行结束时间和后置任务的编排完成时间

thenAync方法

public CompletableFuture<Void> thenRun(Runnable action) {
	// 第一个入参:线程池
	// action:任务
	return uniRunStage(null, action);
}

private CompletableFuture<Void> uniRunStage(Executor e, Runnable f) {
	if (f == null) throw new NullPointerException();
	CompletableFuture<Void> d = new CompletableFuture<Void>();
	if (
		// 判断线程池是否为nul
		e != null || 
		// 尝试执行下任务,判断是否成功
		!d.uniRun(this, f, null)) {
		// 线程池不为null,或者uniRun方法中执行任务失败
		// 封装UniRun对象
		// e:线程池
		// d:后置任务的CompletableFuture对象
		// this:前置任务的CompletableFuture对象
		// f:要执行的任务
		UniRun<T> c = new UniRun<T>(e, d, this, f);
		// 尝试将UniRun对象压栈
		// 如果压栈失败,将NEXT属性赋值为将要执行的任务
		push(c);
		// tryFire方法中还是会尝试执行下任务
		c.tryFire(SYNC);
	}
	// 返回后置任务的CompletableFuture对象
	return d;
}

final void push(UniCompletion<?,?> c) {
	if (c != null) {
		// result是前置任务的CompletableFuture对象中的
		// result为null说明前置任务还没有执行完成
		// tryPushStack方法是将任务压栈
		while (result == null && !tryPushStack(c))
			// 前置任务已经执行完成或者任务压栈失败
			// 将NEXT属性赋值为将要执行的任务
			lazySetNext(c, null);
	}
}

final boolean tryPushStack(Completion c) {
	Completion h = stack;
	lazySetNext(c, h);
	return UNSAFE.compareAndSwapObject(this, STACK, h, c);
}

static void lazySetNext(Completion c, Completion next) {
	// NEXT属性是将要执行的下一个任务
	UNSAFE.putOrderedObject(c, NEXT, next);
}

步骤:

  1. 声明后置任务的CompletableFuture对象
  2. 判断线程池是否为null,如果不为null
  3. 声明UniRun对象
  4. 尝试将UniRun对象压栈,如果压栈失败,将NEXT属性赋值为UniRun对象
  5. 调用tryFire方法,尝试执行任务
  6. 如果线程池为null,调用uniRun方法,尝试执行任务。如果执行成功,返回后置任务的CompletableFuture对象;如果执行失败,走3、4、5步
  7. 返回后置任务的CompletableFuture对象

UniRun内部类

static final class UniRun<T> extends UniCompletion<T,Void> {
	Runnable fn;
	// executor:线程池
	// dep:后置任务的CompletableFuture对象
	// src:前置任务的CompletableFuture对象
	// fn:后置任务
	UniRun(Executor executor, CompletableFuture<Void> dep,
		   CompletableFuture<T> src, Runnable fn) {
		super(executor, dep, src); this.fn = fn;
	}
	final CompletableFuture<Void> tryFire(int mode) {
		CompletableFuture<Void> d; CompletableFuture<T> a;
		if (
			// 健壮性判断,后置任务的CompletableFuture对象是否为null
			(d = dep) == null ||
			// 执行任务
			!d.uniRun(a = src, fn, mode > 0 ? null : this))
			return null;
		dep = null; src = null; fn = null;
		return d.postFire(a, mode);
	}
}
uniRun方法
// a:前置任务的CompletableFuture对象
// f:后置任务
// c:UniRun对象
final boolean uniRun(CompletableFuture<?> a, Runnable f, UniRun<?> c) {
	Object r; Throwable x;
	if (
		// 判断前置任务的CompletableFuture是否为null
		a == null || 
		// 判断前置任务是否执行完毕
		(r = a.result) == null || 
		// 判断后置任务是否为null
		f == null)
		// 进入这里说明前置任务还没有执行完毕
		// 后置任务不执行,返回false
		return false;
	// 判断后置任务是否执行完成
	if (result == null) {
		// 判断前置任务是否异常结束
		if (r instanceof AltResult && (x = ((AltResult)r).ex) != null)
			// 前置任务异常结束,后置任务不执行
			// 将前置任务的异常结果封装并返回
			completeThrowable(x, r);
		else
			try {
				// 判断UniRun对象是否不为null,一般没有传线程池时才为null
				// 如果不为null,执行claim方法
				// claim方法是用线程池异步执行后置任务
				if (c != null && !c.claim())
					// 执行失败,返回false
					return false;
				// 同步执行后置任务
				f.run();
				// 封装正常结果
				completeNull();
			} catch (Throwable ex) {
				// 封装异常结果
				completeThrowable(ex);
			}
	}
	// 任务执行完成,返回true
	return true;
}

postComplete方法

此方法是要执行栈中的后置任务

final void postComplete() {
	// f:前置任务的CompletableFuture
	// h:存储后置任务的栈
	CompletableFuture<?> f = this; Completion h;
	while (
			// 判断栈是否为null
			(h = f.stack) != null ||
			// 循环过程中,判断栈中的任务是否都执行完成
			// 以及栈不为null
		    (f != this && (h = (f = this).stack) != null)) {
		CompletableFuture<?> d; Completion t;
		// 取出栈顶的任务
		if (f.casStack(h, t = h.next)) {
			if (t != null) {
				if (f != this) {
					pushStack(h);
					continue;
				}
				h.next = null;
			}
			// 执行栈顶的任务,以及栈顶任务中嵌套的任务
			f = (d = h.tryFire(NESTED)) == null ? this : d;
		}
	}
}
// mode为-1
final CompletableFuture<Void> tryFire(int mode) {
	// d:后置任务的CompletableFuture对象
	// a:前置任务的CompletableFuture对象
	CompletableFuture<Void> d; CompletableFuture<T> a;
	if (
		// 判断后置任务的CompletableFuture对象是否为null
		(d = dep) == null ||
		// 执行后置任务
		!d.uniRun(a = src, fn, mode > 0 ? null : this))
		return null;
	dep = null; src = null; fn = null;
	// 执行后置任务中嵌套的后置任务
	return d.postFire(a, mode);
}