【JUC】第六章 Fork/Join 框架、CompletableFuture

第六章 Fork/Join 框架、CompletableFuture

一、Fork/Join 框架

1.简介

Fork/Join 框架可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出

  • Fork:把一个复杂任务进行拆分,大事化小
  • Join:把拆分任务的结果进行合并

Fork/Join 框架要完成两件事:

  • 任务分割:
    首先 Fork/Join 框架需要把大的任务拆分成足够小的子任务,如果子任务比较大的话还要对子任务进行继续拆分
  • 执行任务并合并结果:
    被拆分的子任务分别放到双端队列里,然后几个启动线程分别从双端队列里获取任务执行,子任务执行完的结果都放在另外一个队列里。最后启动一个线程从队列里获取数据,合并这些数

2.Fork/Join 框架的两个核心类

ForkJoinTask

就是执行具体处理逻辑的类,该类提供了在任务中执行 fork 和 join 的机制。通常情况下,我们不需要直接继承 ForkJoinTask 类,只需要继承它的两个子类:

  • RecursiveAction:用于没有返回结果的任务
  • RecursiveTask:用于有返回结果的任务

ForkJoinPool

ForkJoinTask 需要通过 ForkJoinPool 来执行

通过 ForkJoinPool forJoinPool = ForkJoinPool.commonPool() 来获得,也可通过构造,自定义设置属性来获取,一般使用前者就可以

3.Fork 方法

Fork 方法的实现原理是,当我们调用 ForkJoinTask 的 fork 方法时,程序会把任务放在 ForkJoinWorkerThread 的 pushTask 的 workQueue 中,异步地执行这个任务,然后立即返回结果

	public final ForkJoinTask<V> fork() {
    Thread t;
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
        ((ForkJoinWorkerThread)t).workQueue.push(this);
    else
        ForkJoinPool.common.externalPush(this);
    return this;
}

pushTask 方法把当前任务存放在 ForkJoinTask 数组队列里,然后再调用 ForkJoinPool 的 signalWork() 唤醒或创建一个工作线程来执行任务

final void push(ForkJoinTask<?> task) {
    ForkJoinTask<?>[] a; ForkJoinPool p;
    int b = base, s = top, n;
    if ((a = array) != null) {    // ignore if queue removed
        int m = a.length - 1;     // fenced write for task visibility
        U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
        U.putOrderedInt(this, QTOP, s + 1);
        if ((n = s - b) <= 1) {
            if ((p = pool) != null)
                p.signalWork(p.workQueues, this);
        }
        else if (n >= m)
            growArray();
    }
}

4.Join 方法

Join 方法的主要作用是阻塞当前线程并等待获取结果

public final V join() {
    int s;
    if ((s = doJoin() & DONE_MASK) != NORMAL)
        reportException(s);
    return getRawResult();
}

它首先调用 doJoin 方法,通过 doJoin() 得到当前任务的状态来判断返回什么结果,任务状态有 NORMAL(已完成)、CANCELLED(被取消)、SIGNAL(信号)、EXCEPTIONAL(出现异常)

  • 如果任务状态是已完成,则直接返回任务结果
  • 如果任务状态是被取消,则直接抛出 CancellationException
  • 如果任务状态是抛出异常,则直接抛出对应的异常
private int doJoin() {
    int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
    return (s = status) < 0 ? s :
            ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
                    (w = (wt = (ForkJoinWorkerThread)t).workQueue).
                            tryUnpush(this) && (s = doExec()) < 0 ? s :
                            wt.pool.awaitJoin(w, this, 0L) :
                    externalAwaitDone();
}
final int doExec() {
    int s; boolean completed;
    if ((s = status) >= 0) {
        try {
            completed = exec();
        } catch (Throwable rex) {
            return setExceptionalCompletion(rex);
        }
        if (completed)
            s = setCompletion(NORMAL);
    }
    return s;
}

doJoin() 流程如下:

  1. 首先通过查看人任务的状态,看任务是否已执行完成,如果执行完成,则直接返回任务状态
  2. 如果没有执行完,则从任务数组里取出任务并执行
  3. 如果任务顺利执行完成,则设置任务状态为 NORMAL,如果出现异常,则记录异常,并将任务状态设置为 EXCEPTIONAL

5.Fork/Join 框架的异常处理

ForkJoinTask 在执行的时候可能会抛出异常,但是我们没办法在主线程里直接捕获异常,所以 ForkJoinTask 提供了 isCompletedAbnormally() 来检查任务是否已经抛出异常或已经被取消了,并且可以通过 ForkJoinTask 的getException 方法获取异常

getException 方法返回 Throwable 对象,如果任务被取消了则返回 CancellationException,如果任务没有完成或者没有抛出异常则返回 null

6.入门案例

生成一个计算任务,计算 1 + 2 + 3 + …… + 100,每 10 个数切分一个子任务

package com.sisyphus.forkjoin;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

/**
 * @Description: $
 * @Param: $
 * @return: $
 * @Author: Sisyphus
 * @Date: 8/20$
 */

class MyTask extends RecursiveTask<Integer> {
    //拆分差值不能超过 10,计算 10 以内的运算
    private static final Integer VALUE = 10;
    private int begin;  //拆分开始值
    private int end;    //拆分结束值
    private int result; //返回结果

    //创建有参构造
    public MyTask(int begin, int end){
        this.begin = begin;
        this.end = end;
    }

    //拆分和合并过程
    @Override
    protected Integer compute(){
        //判断相加的两个数相差是否大于 10
        if ((end - begin) <= 10){
            for(int i = begin; i <= end; i++){
                result = result + i;
            }
        }else{  //进一步拆分
            //获取中间值
            int middle = (begin + end) / 2;
            //拆分左边
            MyTask task01 = new MyTask(begin, middle);
            //拆分右边
            MyTask task02 = new MyTask(middle + 1, end);
            //调用方法拆分
            task01.fork();
            task02.fork();
            //合并结果
            result = task01.join() + task02.join();
        }
        return result;
    }
}

public class ForkJoinDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建 MyTask 对象
        MyTask myTask = new MyTask(0,100);
        //创建分支合并池对象
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(myTask);
        //获取最终合并之后的结果
        Integer result = forkJoinTask.get();
        System.out.println(result);
        //关闭池对象
        forkJoinPool.shutdown();
    }
}

在这里插入图片描述

二、CompletableFuture

1.简介

CompletableFuture 在 Java 里面被用于异步编程,异步通常意味着非阻塞,可以使得我们的任务单独运行在与主线程分离的其它线程中,并且通过回调可以在主线程中得到异步任务的执行状态,是否完成,和是否异常等信息

CompletableFuture 实现了 Future 和 CompletionStage 接口,实现了 Future 接口就可以兼容现有的线程池框架,而实现了 CompletionStage 接口就可以使用其中定义的多种异步方法

2.Future 回顾

Future 通常用来表示一个异步任务的引用,比如我们将任务提交到线程池里面,然后我们会得到一个 Future,在 Future 里面有 isDone 方法来判断任务是否处理结束,还有 get 方法可以一直阻塞直到任务结束然后获取结果,但整体来说,这种方式还是同步的,因为需要客户端不断阻塞等待或者不断轮询才能直到任务是否完成

Future 的主要缺点如下

  • 不支持手动完成
    我们交了一个任务,但是执行太慢了,而且我通过其他路径已经获取到了任务结果,但是却没法把这个任务结果通知到正在执行的线程,所以必须主动取消或者一直等待它执行完成
  • 不支持进一步的非阻塞调用
    通过 Future 的 get 方法会一直阻塞到任务完成,如果想在获取任务之后执行额外的任务,这是无法实现的,因为 Future 不支持回调函数
  • 不支持链式调用
    对于 Future 的执行结果,如果想继续传到下一个 Future 处理使用,从而形成一个链式的 pipline 调用,这是 Future 中无法实现的
  • 不支持多个 Future 合并
    比如我们有 10 个Future 并行执行,如果我们想在所有的 Future 运行完毕之后,执行某些函数,是无法通过 Future 实现的
  • 不支持异常处理
    Future 的 API 没有任何的异常处理的 api,所以在异步运行时,如果出了问题是不好定位的

2.CompletableFuture 入门

使用 CompletableFuture

主线程里创建一个 CompletableFuture,然后主线程调用 get 方法会阻塞,最后我们在一个子线程中使其终止

public static void main(String[] args) throws Exception{
	CompletableFuture<String> future = new CompletableFuture<>();
	new Thread(()->{
		try{
			System.out.println(Thread.currentThread().getName() + "子线程开始干活");
			//子线程睡眠 5 秒
			Thread.sleep(5000);
			//在子线程中完成主线程
			future.complete("success");
		}catch(Exception e){
			e.printStackTrace();
		}
	},"A").start();
	//主线程调用 get 方法阻塞
	System.out.println("主线程调用 get 方法获取结果为:" + future.get());
	System.out.println("主线程完成,阻塞结束");
}

没有返回值的异步任务

public static void main(String[] args) throws Exception{
	System.out.println("主线程开始");
	//运行一个没有返回值的异步任务
	CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
		try{
			System.out.println("子线程启动干活");
			Thread.sleep(5000);
			System.out.println("子线程完成")}catch(Exception e){
			e.printStackTrace();
		}		
	});

	//主线程阻塞
	future.get();
	System.out.println("主线程结束")
}

有返回值的异步任务

public static void main(Stringp[] args) throws Exception{
	System,out.println("主线程开始");
	//运行一个有返回值的异步任务
	CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
		try{
			System.out.println("子线程开始任务");
			Thread.sleep(5000);
		}catch(Exception e){
			e.printStackTrace();
		}
		return "子线程完成了"
	});
	
	//主线程阻塞
	String s = future.get();
	System.out.println("主线程结束,子线程的结果为:" + s);
}

线程依赖

当一个线程依赖另一个线程时,可以使用 thenApply 方法来把这两个线程串行化

private static Integer num = 10;

public static void main(String[] args) throws Exception{
	System.out.println("主线程开始");
	CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
		try{
			System.out.println("加 10 任务开始"):
			num += 10;
		}catch(Exception e){
			e.printStackTrace();
		}
		return num;
	}).thenApply(integer -> {
		return num * num;
	});

	Integer integer = future.get();
	System.out.println("主线程结束,子线程的结果为:" + integer);
}

消费处理结果

thenAccept 消费处理结果,接受任务的处理结果并消费处理,无返回结果

public static void main(String[] args) throws Exception{
	System.out.println("主线程开始");
	CompletableFuture.supplyAsync(()->{
		try{
			System.out.println("加 10 任务开始");
			num += 10;
		}catch(Exception e){
			e.printStackTrace();
		}
		return num;
	}).thenApply(integer -> {
		return num * num;
	}).thenAccept(new Consumer<Integer>(){
		@Override
		public void accept(Integer integer){
			System.out.println("子线程全部处理完成,最后调用了 accept,结果为:" + integer);
		)
	});
}

异常处理

exceptionally 异常处理,出现异常时触发

public static void main(String[] args) throows Exception{
	System.out.println("主线程开始");
	CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
		int i = 1 / 0;
		System.out.println("加 10 任务开始");
		num += 10;
		return num;
	}).exceptionally(ex -> {
		System.out.println(ex.getMessage());
		return -1;
	});
	System.out.println(future.get());
}

handle 类似于 thenAccept/thenRun 方法,但是同时也可以处理异常

public static void main(String[] args) throws Exception{
	System.out.println("主线程开始");
	CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
		System.out.println("加 10 任务开始"):
		num += 10;
		return num;
	}).handle(i,ex) -> {
		System.out.println("进入 handle 方法");
		if(ex !+ null){
			System.out.println("发生了异常,内容为:" + ex.getMessage());
			return -1;
		}else{
			System.out.println("正常完成,内容为:" + i);
			return i;
		}
	});
	System.out.println(future.get());
}

结果合并

thenCompose 合并两个有依赖关系的 CompletableFutures 的执行结果

public static void main(String[] args) throws Exception{
	System.out.println("主线程开始")//第一步加 10
	CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
		System.out.println("加 10 任务开始");
		num += 10;
		return num;
	});
	//合并
	CompletableFuture<Integer> future1 = future.thenCompose(i -> 
		//再来一个 CompletableFuture
		CompletableFuture.supplyAsync(()->{
			return i + 1;
		}));
	System.out.println(future.get());
	System.out.println(future1.get());
}

thenCombine 合并两个没有依赖关系的 CompletableFutures 任务

public static void main(String[] args) throws Exception{
	System.out.println("主线程开始");

	CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {
		System.out.println("加 10 任务开始");
		num += 10;
		return num;
	});

	CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() -> {
		System.out.println("乘 10 任务开始");
		num = num * 10;
		return num;
	});

	//合并两个结果
	CompletableFuture<Object> future = job1.thenCombine(job2, new BiFunction<Integer, Integer, List<Integer>>(){
		@Override
		public List<Integer> apply(Integer a, Integer b){
			List<Integer> list = new ArrayList<>();
			list.add(a);
			list.add(b);
			return list;
		}
	));
	System.out.println("合并结果为:" + future.get());
}		

allOf:合并一系列独立地 future 任务,等所有的任务执行完后再做一些事情

public static void main(String[] args) throws Exception{
	System.out.println("主线程开始");
	
	List<CompletableFuture> list = new ArrayList<>();
	
	CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {
		System.out.println("加 10 任务开始");
		num = num + 10;
		return num;
	});
	list.add(job1);

	CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() - > {
		System.out.println("乘 10 任务开始");
		num = num * 10;
		reutrn num;
	});
	list.add(job2);
		
	CompletableFuture<Integer> job3 = CompletableFuture.supplyAsync(() -> {
		System.out.println("减 10 任务开始");
		num = num - 10;
		return num;
	});
	list.add(job3);

	CompletableFuture<Integer> job4 = CompletableFuture.supplyAsync(() => {
		System.out.println("除 10 任务开始");
		num = num / 10;
		return num;
	});
	list.add(job4);

	//多任务合并
	List<Integer> collect = list.stream().map(CompletableFuture<Integer>::join).collect(Collectors.toList());
	System.out.println(collect);

anyOf:只要在多个 future 里面有一个返回,整个任务就可以结束,而不需要等所有 future 结束

public static void main(String[] args) throws Exception{
	System.out.println("主线程开始");
	
	CompletableFuture<Integer>[] futures = new CompletableFuture[4];
	
	CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {
		try{
			Thread.sleep(5000);
			System.out.println("加 10 任务开始");
			num = num + 10;
			return num;
		}catch(Exception e){
			return 0;
		}
	});
	future[0] = job1;

	CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() -> {
		try{
			Thread.sleep(2000);
			System.out.println("乘 10 任务开始");
			num = num * 10;
			return num;
		}catch(Exception e){
			return 1;
		}
	});
	futures[1] = job2;

	CompletableFuture<Integer> job3 = CompletableFuture.supplyAsync(() -> {
		try{
			Thread.sleep(3000);
			System.out.println("减 10 任务开始");
			num = num - 10;
			return num;
		}catch(Exception e){
			return 2;
		}
	});
	futures[2] = job3;

	CompletableFuture<Integer> job4 = CompletableFuture.supplyAsync(() -> {
		try{
			Thread.sleep(4000);
			System.out.println("除 10 任务开始");
			num = num / 10;
			return num;
		}catch(Exception e){
			return 3;
		}
	});
	futures[3] = job4;

	CompletableFuture<Object> future = CompletableFuture.anyOf(futures);
	System.out.println(future.get());
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值