JUC下的异步编程工具使用详情以及源码分析(FutureTask、CompletableFuture)

异步编程

一、FutureTask应用&源码分析

1.1 FutureTask介绍

FutureTask是一个可以取消异步任务的类。FutureTaskFuture做的一个基本实现。可以调用方法区开始和取消一个任务

一般是配合Callable去使用

异步任务启动之后,可以获取一个绑定当前异步任务的FutureTask

可以基于FutureTask的方法去取消任务,查看任务是否结果,以及获取任务的返回结果

FutureTask内部的整体结构中,实现了RunnableFuture的接口,这个接口又继承了RunnableFuture这个两个接口。所以FutureTask也可以作为任务直接交给线程池去处理

1.2 FutureTask应用

大方向是FutureTask对任务的控制:

  • 任务执行过程中状态的控制
  • 任务执行完毕后,返回结果的获取

FutureTask的任务在执行run方法后,是无法被再次运行,需要使用runAndReset方法才可以

public static void main(String[] args) throws InterruptedException {
    // 构建FutureTask,基于泛型执行返回结果类型
    // 在有参构造中,声明Callable或者Runnable指定任务
    FutureTask<String> futureTask = new FutureTask<>(() -> {
        System.out.println("任务开始执行……");
        Thread.sleep(2000);
        System.out.println("任务执行完毕……");
        return "OK!";
    });

    // 构建线程池
    ExecutorService service = Executors.newFixedThreadPool(10);

    // 线程池执行任务
    service.execute(futureTask);

    // futureTask提供了run方法,一般不会自己去调用run方法,让线程池去执行任务,由线程池去执行run方法
    // run方法在执行时,是有任务状态的。任务已经执行了,再次调用run方法无效的。
    // 如果希望任务可以反复被执行,需要去调用runAndReset方法
//        futureTask.run();

    // 对返回结果的获取,类似阻塞队列的poll方法
    // 如果在指定时间内,没有拿到方法的返回结果,直接扔TimeoutException
//        try {
//            String s = futureTask.get(3000, TimeUnit.MILLISECONDS);
//            System.out.println("返回结果:" + s);
//        } catch (Exception e) {
//            System.out.println("异常返回:" + e.getMessage());
//            e.printStackTrace();
//        }

    // 对返回结果的获取,类似阻塞队列的take方法,死等结果
//        try {
//            String s = futureTask.get();
//            System.out.println("任务结果:" + s);
//        } catch (ExecutionException e) {
//            e.printStackTrace();
//        }

    // 对任务状态的控制
//        System.out.println("任务结束了么?:" + futureTask.isDone());
//        Thread.sleep(1000);
//        System.out.println("任务结束了么?:" + futureTask.isDone());
//        Thread.sleep(1000);
//        System.out.println("任务结束了么?:" + futureTask.isDone());
}

1.3 FutureTask源码分析

FutureTask的源码,要从几个方向去看:

  • 先查看FutureTask中提供的一些状态
  • 再查看任务的执行过程
1.3.1 FutureTask中的核心属性

清楚任务的流转流转状态是怎样的,其次对于核心属性要追到是干嘛的

/**
 FutureTask的核心属性
 FutureTask任务的状态流转
 * NEW -> COMPLETING -> NORMAL           任务正常执行,并且返回结果也正常返回
 * NEW -> COMPLETING -> EXCEPTIONAL      任务正常执行,但是结果是异常(执行途中出现异常)
 * NEW -> CANCELLED                      任务被取消   
 * NEW -> INTERRUPTING -> INTERRUPTED    任务被中断
 */
// 记录任务的状态
private volatile int state;
// 任务被构建之后的初始状态
private static final int NEW          = 0;
private static final int COMPLETING   = 1;
private static final int NORMAL       = 2;
private static final int EXCEPTIONAL  = 3;
private static final int CANCELLED    = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED  = 6;

/** 需要执行任务,会被赋值到这个属性 */
private Callable<V> callable;
/** 任务的任务结果要存储在这几个属性中 */
private Object outcome; // non-volatile, protected by state reads/writes
/** 执行任务的线程 */
private volatile Thread runner;
/** 等待返回结果的线程Node对象, */
private volatile WaitNode waiters;
static final class WaitNode {
    volatile Thread thread;
    volatile WaitNode next;
    WaitNode() { thread = Thread.currentThread(); }
}
1.3.2 FutureTask的run方法

任务执行前的一些判断,以及调用任务封装结果的方式,还有最后的一些后续处理

// 当线程池执行FutureTask任务时,会调用的方法
public void run() {
    // 如果当前任务状态不是NEW,直接return告辞
    if (state != NEW ||  
        // 如果状态正确是NEW,这边需要基于CAS将runner属性设置为当前线程
        // 如果CAS失败,直接return告辞
        !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
        return;

    try {
        // 将要执行的任务拿到
        Callable<V> c = callable;
        // 健壮性判断,保证任务不是null
        // 再次判断任务的状态是NEW(DCL)
        if (c != null && state == NEW) {
            // 执行任务
            // result:任务的返回结果
            // ran:如果为true,任务正常结束。 如果为false,任务异常结束。
            V result;
            boolean ran;
            try {
                // 执行任务
                result = c.call();
                // 正常结果,ran设置为true
                ran = true;
            } catch (Throwable ex) {
                // 如果任务执行期间出了异常
                // 返回结果置位null
                result = null;
                // ran设置为false
                ran = false;
                // 封装异常结果
                setException(ex);
            }
            if (ran)
                // 封装正常结果
                set(result);
        }
    } finally {
        // 将执行任务的线程置位null
        runner = null;
        // 拿到任务的状态
        int s = state;
        // 如果状态大于等于INTERRUPTING
        if (s >= INTERRUPTING)
            // 进来代表任务中断,做一些后续处理
            handlePossibleCancellationInterrupt(s);
    }
}
1.3.3 FutureTask的set&setException方法

任务执行完毕后,修改任务的状态以及封装任务的结果

// 没有异常的时候,正常返回结果
protected void set(V v) {
    // 因为任务执行完毕,需要将任务的状态从NEW,修改为COMPLETING
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        // 将返回结果赋值给 outcome 属性
        outcome = v;
        // 将任务状态变为NORMAL,正常结束
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL);
        // 一会再说……
        finishCompletion();
    }
}

// 任务执行期间出现了异常,这边要封装结果
protected void setException(Throwable t) {
    // 因为任务执行完毕,需要将任务的状态从NEW,修改为COMPLETING
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        // 将异常信息封装到 outcome 属性
        outcome = t;
        // 将任务状态变为EXCEPTIONAL,异常结束
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); 
        // 一会再说……
        finishCompletion();
    }
}
1.3.4 FutureTask的cancel方法

任务取消的一个方式

  • 任务直接从NEW状态转换为CANCEL
  • 任务从NEW状态变成INTERRUPTING,然后再转换为INTERRUPTED
// 取消任务操作
public boolean cancel(boolean mayInterruptIfRunning) {
    // 查看任务的状态是否是NEW,如果NEW状态,就基于传入的参数mayInterruptIfRunning
    // 决定任务是直接从NEW转换为CANCEL,还是从NEW转换为INTERRUPTING
    if (!(state == NEW && 
        UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    try {   
        // 如果mayInterruptIfRunning为true
        // 就需要中断线程
        if (mayInterruptIfRunning) {
            try {
                // 拿到任务线程
                Thread t = runner;
                if (t != null)
                    // 如果线程不为null,直接interrupt
                    t.interrupt();
            } finally { 
                // 将任务状态设置为INTERRUPTED
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
        // 任务结束后的一些处理~~ 一会看~~
        finishCompletion();
    }
    return true;
}

测试

public static void main(String[] args) throws InterruptedException, ExecutionException {
 // 构建FutureTask,基于泛型执行返回结果类型
 // 在有参构造中,声明Callable或者Runnable指定任务
 FutureTask<String> futureTask = new FutureTask<>(() -> {
     System.out.println("任务开始执行……");
     Thread.sleep(2000);
     System.out.println("任务执行完毕……");
     return "OK!";
 });
 // 构建线程池
 ExecutorService service = Executors.newFixedThreadPool(10);

 // 线程池执行任务
 service.execute(futureTask);
 Thread.sleep(1000);
 System.out.println(futureTask.cancel(true));
 System.out.println(futureTask.get());
}

console

任务开始执行……
true
Exception in thread "main" java.util.concurrent.CancellationException
	at java.util.concurrent.FutureTask.report(FutureTask.java:121)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.armin.thread.tool.FutureTaskTest.main(FutureTaskTest.java:26)
  • futureTask.cancel(true):会中断执行的程序
  • futureTask.cancel(false):不会中断执行的程序,任务会执行完,但get()同样会抛出CancellationException异常

如果在等待线程的任务执行完毕后再调用cancel()返回falseget()同样也会拿到返回值与正常情况无异

1.3.5 FutureTask的get方法

这个是线程获取FutureTask任务执行结果的方法

// 拿任务结果
public V get() throws InterruptedException, ExecutionException {
    // 获取任务的状态
    int s = state;
    // 要么是NEW,任务还没执行完
    // 要么COMPLETING,任务执行完了,结果还没封装好。
    if (s <= COMPLETING)
        // 让当前线程阻塞,等待结果
        s = awaitDone(false, 0L);
    // 最终想要获取结果,需要执行report方法
    return report(s);
}

// 线程等待FutureTask结果的过程
private int awaitDone(boolean timed, long nanos) throws InterruptedException {
    // 针对get方法传入了等待时长时,需要计算等到什么时间点
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    // 声明好需要的Node,queued:放到链表中了么?
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
        // 查看线程是否中断,如果中断,从等待链表中移除,甩个异常
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }
        // 拿到状态
        int s = state;
        // 到这,说明任务结束了。
        if (s > COMPLETING) {
            if (q != null)
                // 如果之前封装了WaitNode,现在要清空
                q.thread = null;
            return s;
        }
        // 如果任务状态是COMPLETING,这就不需要去阻塞线程,让步一下,等待一小会,结果就有了
        else if (s == COMPLETING) 
            Thread.yield();
        // 如果还没初始化WaitNode,初始化
        else if (q == null)
            q = new WaitNode();
        // 没放队列的话,直接放到waiters的前面
        else if (!queued)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                 q.next = waiters, q);
        // 准备挂起线程,如果timed为true,挂起一段时间
        else if (timed) {
            // 计算出最多可以等待多久
            nanos = deadline - System.nanoTime();
            // 如果等待的时间没了
            if (nanos <= 0L) {
                // 移除当前的Node,返回任务状态
                removeWaiter(q);
                return state;
            }
            // 等一会
            LockSupport.parkNanos(this, nanos);
        }
        else
            // 死等
            LockSupport.park(this);
    }
}

// get的线程已经可以阻塞结束了,基于状态查看能否拿到返回结果
private V report(int s) throws ExecutionException {
    // 拿到outcome 返回结果
    Object x = outcome;
    // 如果任务状态是NORMAL,任务正常结束,返回结果
    if (s == NORMAL)
        return (V)x;
    // 如果任务状态大于等于取消
    if (s >= CANCELLED)
        // 直接抛出异常
        throw new CancellationException();
    // 到这就是异常结束
    throw new ExecutionException((Throwable)x);
}
1.3.6 FutureTask的finishCompletion方法

只要任务结束了,无论是正常返回,异常返回,还是任务被取消都会执行这个方法

而这个方法其实就是唤醒那些执行get方法等待任务结果的线程

// 任务结束后触发
private void finishCompletion() {
    // 在任务结束后,需要唤醒
    for (WaitNode q; (q = waiters) != null;) {
        // 第一步直接以CAS的方式将WaitNode置为null
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            for (;;) {
                // 拿到了Node中的线程
                Thread t = q.thread;
                // 如果线程不为null
                if (t != null) {
                    // 第一步先置位null
                    q.thread = null;
                    // 直接唤醒这个线程
                    LockSupport.unpark(t);
                }
                // 拿到当前Node的next
                WaitNode next = q.next;
                // next为null,代表已经将全部节点唤醒了吗,跳出循环
                if (next == null)
                    break;
                // 将next置位null
                q.next = null; 
                // q的引用指向next
                q = next;
            }
            break;
        }
    }

    // 任务结束后,可以基于这个扩展方法,记录一些信息
    done();

    // 任务执行完,把callable具体任务置位null
    callable = null;  
}

扩展口

可以继承FutureTask重写done()方法实现线程在执行完毕后(不论正常还是异常执行结束)的一系列操作

二、CompletableFuture应用&源码分析

2.1 CompletableFuture介绍

平时多线程开发一般就是使用RunnableCallableThreadFutureTaskThreadPoolExecutor这些内容和并发编程息息相关。相对来对来说成本都不高,多多使用是可以熟悉这些内容。这些内容组合在一起去解决一些并发编程的问题时,很多时候没有办法很方便的去完成异步编程的操作

Thread + Runnable:执行异步任务,但是没有返回结果

Thread + Callable + FutureTask:完整一个可以有返回结果的异步任务

  • 获取返回结果,如果基于get方法获取,线程需要挂起在WaitNode
  • 获取返回结果,也可以基于isDone判断任务的状态,但是这里需要不断轮询

上述的方式都是有一定的局限性的。

比如说任务A,任务B,还有任务C。其中任务B还有任务C执行的前提是任务A先完成,再执行任务B和任务C

如果任务的执行方式逻辑比较复杂,可能需要业务线程导出阻塞等待,或者是大量的任务线程去编一些任务执行的业务逻辑。对开发成本来说比较高

CompletableFuture就是帮你处理这些任务之间的逻辑关系,编排好任务的执行方式后,任务会按照规划好的方式一步一步执行,不需要让业务线程去频繁的等待

2.2 CompletableFuture应用

CompletableFuture应用还是需要一内内的成本的

首先对CompletableFuture提供的函数式编程中三个函数有一个掌握

  • Supplier:生产者,没有入参,有返回结果
  • Consumer:消费者,有入参,但是没有返回结果
  • Function:函数,有入参,又有返回结果
2.2.1 supplyAsync

CompletableFuture如果不提供线程池的话,默认使用的ForkJoinPool,而ForkJoinPool内部是守护线程,如果main线程结束了,守护线程会跟着一起结束

public static void main(String[] args)  {
    // 生产者,可以指定返回结果
    CompletableFuture<String> firstTask = CompletableFuture.supplyAsync(() -> {
        System.out.println("异步任务开始执行");
        System.out.println("异步任务执行结束");
        return "返回结果";
    });

    String result1 = firstTask.join();
    String result2 = null;
    try {
        result2 = firstTask.get();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }

    System.out.println(result1 + "," + result2);
}

console

异步任务开始执行
异步任务执行结束
返回结果,返回结果

Process finished with exit code 0
2.2.2 runAsync

当前方式既不会接收参数,也不会返回任何结果,非常基础的任务编排方式

public static void main(String[] args) throws IOException {
    CompletableFuture.runAsync(() -> {
        System.out.println("任务go");
        System.out.println("任务done");
    });

    System.in.read();
}

console

任务go
任务done
2.2.3 thenApply,thenApplyAsync

有任务A,还有任务B

任务B需要在任务A执行完毕后再执行

而且任务B需要任务A的返回结果

任务B自身也有返回结果

thenApply可以拼接异步任务,前置任务处理完之后,将返回结果交给后置任务,然后后置任务再执行(使用的同一个线程执行)

thenApply提供了带有Async的方法,可以指定每个任务使用的具体线程池

public static void main(String[] args) throws IOException {
    ExecutorService executor = Executors.newFixedThreadPool(10);

    /*CompletableFuture taskA = CompletableFuture.supplyAsync(() -> {
        String id = UUID.randomUUID().toString();
        System.out.println("执行任务A:" + id);
        return id;
    });
    CompletableFuture taskB = taskA.thenApply(result -> {
        System.out.println("任务B获取到任务A结果:" + result);
        result = result.replace("-", "");
        return result;
    });

    System.out.println("main线程拿到结果:" + taskB.join());*/

    CompletableFuture<String> taskB = CompletableFuture.supplyAsync(() -> {
        String id = UUID.randomUUID().toString();
        System.out.println("执行任务A:" + id + "," + Thread.currentThread().getName());
        return id;
    }).thenApplyAsync(result -> {
        System.out.println("任务B获取到任务A结果:" + result + "," + Thread.currentThread().getName());
        result = result.replace("-", "");
        return result;
    },executor);

    System.out.println("main线程拿到结果:" + taskB.join());
}
2.2.4 thenAccept,thenAcceptAsync

套路和thenApply一样,都是任务A和任务B的拼接

前置任务需要有返回结果,后置任务会接收前置任务的结果,返回后置任务没有返回值

public static void main(String[] args) throws IOException {
    CompletableFuture.supplyAsync(() -> {
        System.out.println("任务A");
        return "abcdefg";
    }).thenAccept(result -> {
        System.out.println("任务b,拿到结果处理:" + result);
    });

    System.in.read();
}
2.2.5 thenRun,thenRunAsync

套路和thenApplythenAccept一样,都是任务A和任务B的拼接

前置任务没有返回结果,后置任务不接收前置任务结果,后置任务也会有返回结果

public static void main(String[] args) throws IOException {
    CompletableFuture.runAsync(() -> {
        System.out.println("任务A!!");
    }).thenRun(() -> {
        System.out.println("任务B!!");
    });
  
    System.in.read();
}

thenRunAsyncthenAccept一致,只是说可以传线程池进行执行,如果不传则使用默认线程池

private static final Executor asyncPool = useCommonPool ?
     ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
2.2.6 thenCombine,thenAcceptBoth,runAfterBoth

比如有任务A,任务B,任务C。任务A和任务B并行执行,等到任务A和任务B全部执行完毕后,再执行任务C

A+B ------ C

基于前面thenApplythenAcceptthenRun知道了一般情况三种任务的概念

thenCombine以及thenAcceptBoth还有runAfterBoth的区别是一样的

public static void main(String[] args) throws IOException {
	long start = System.currentTimeMillis();
    CompletableFuture<Integer> taskC = CompletableFuture.supplyAsync(() -> {
        System.out.println("线程A:" + Thread.currentThread().getName());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 50;
    }).thenCombine(CompletableFuture.supplyAsync(() -> {
        System.out.println("线程B:" + Thread.currentThread().getName());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 49;
    }), (resA, resB) -> {
        System.out.println("线程C:" + Thread.currentThread().getName());
        return resA + resB;
    });
    System.out.println(taskC.join());
    System.out.println("执行时间:" + (System.currentTimeMillis() - start) + "ms");
}

console

线程A:ForkJoinPool.commonPool-worker-9
线程B:ForkJoinPool.commonPool-worker-2
线程C:ForkJoinPool.commonPool-worker-9
99
执行时间:2039ms

Process finished with exit code 0
2.2.7 applyToEither,acceptEither,runAfterEither

比如有任务A,任务B,任务C。任务A和任务B并行执行,只要任务A或者任务B执行任一任务执行完毕,就会开始执行任务C

A or B ----- C

applyToEitheracceptEitherrunAfterEither三个方法拼接任务的方式都是一样的

区别

  • applyToEither:可以接收结果并且返回结果
  • acceptEither:可以接收结果没有返回结果
  • runAfterEither:不接收结果也没返回结果
public static void main(String[] args) throws IOException {
    CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
        System.out.println("任务A, " + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 88;
    }).applyToEither(CompletableFuture.supplyAsync(() -> {
        System.out.println("任务B, " + Thread.currentThread().getName());
        return 77;
    }), resultFirst -> {
        System.out.println("任务C,获取到值:" + resultFirst + " " + Thread.currentThread().getName());
        return resultFirst;
    });
    System.out.println(task.join());
}

console

任务A, ForkJoinPool.commonPool-worker-9
任务B, ForkJoinPool.commonPool-worker-2
任务C,获取到值:77 main
77

Process finished with exit code 0
2.2.8 exceptionally,thenCompose,handle
  • exceptionally:这个也是拼接任务的方式,但是只有前面业务执行时出现异常了,才会执行当前方法来处理只有异常出现时,CompletableFuture的编排任务没有处理完时,才会触发

    public static void main(String[] args) throws IOException {
    	CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务A, " + Thread.currentThread().getName());
            int i = 1/0;
            return 88;
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
            System.out.println("任务B, " + Thread.currentThread().getName());
            return 77;
        }), resultFirst -> {
            System.out.println("任务C,获取到值:" + resultFirst + " " + Thread.currentThread().getName());
            return resultFirst;
        }).exceptionally(e -> {
            // exception
            System.out.println("获取到异常, " + e.getMessage());
            return -1;
        });
        System.out.println("执行结果, " + task.join());
    }
    

    console

    任务A, ForkJoinPool.commonPool-worker-9
    任务B, ForkJoinPool.commonPool-worker-2
    获取到异常, java.lang.ArithmeticException: / by zero
    执行结果, -1
    
    Process finished with exit code 0
    
  • whenComplete:可以拿到返回结果同时也可以拿到出现的异常信息,但是whenComplete本身是Consumer不能返回结果。无法帮你捕获异常(程序执行的最后异常会抛出),但是可以拿到异常返回的结果

    public static void main(String[] args) throws IOException {
    	CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
            System.out.println("任务A, " + Thread.currentThread().getName());
            int i = 1/0;
            return 88;
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
            System.out.println("任务B, " + Thread.currentThread().getName());
            return 77;
        }), resultFirst -> {
            System.out.println("任务C,获取到值:" + resultFirst + " " + Thread.currentThread().getName());
            return resultFirst;
        }).whenComplete((r, e) -> {
            System.out.println("获取到结果:" + r);
            System.out.println("获取到异常, " + e);
            System.out.println("这里会被执行");
        });
        System.out.println("执行结果, " + task.join()); // 此处会被whenComplete抛出的异常打断
    }
    

    console

    任务A, ForkJoinPool.commonPool-worker-9
    任务B, ForkJoinPool.commonPool-worker-2
    获取到结果:null
    获取到异常, java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
    这里会被执行
    Exception in thread "main" java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
    	at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
    	at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
    	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1606)
    	at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1596)
    	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1067)
    	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1703)
    	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:172)
    Caused by: java.lang.ArithmeticException: / by zero
    	at com.armin.thread.tool.CompletableFutureTest.lambda$testWhenComplete$0(CompletableFutureTest.java:28)
    	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1604)
    	... 5 more
    
    Process finished with exit code 1
    
    
  • handle:可以拿到返回结果同时也可以拿到出现的异常信息,并且也可以指定返回托底数据。可以捕获异常的,异常不会抛出去

whenCompletehandle这两个也是异常处理的套路,可以根据方法描述发现,他的功能方向比exceptionally要更加丰富

public static void main(String[] args) throws IOException {
    CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
        System.out.println("任务A, " + Thread.currentThread().getName());
        int i = 1/0;
        return 88;
    }).applyToEither(CompletableFuture.supplyAsync(() -> {
        System.out.println("任务B, " + Thread.currentThread().getName());
        try {
            Thread.sleep(10); // 保证任务A先执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 77;
    }), resultFirst -> {
        System.out.println("任务C,获取到值:" + resultFirst + " " + Thread.currentThread().getName());
        return resultFirst;
    }).handle((r, e) -> {
        System.out.println("获取到结果:" + r);
        System.out.println("获取到异常, " + e);
        System.out.println("这里会被执行");
        return -1;
    });
    System.out.println("执行结果, " + task.join());
}

console

任务A, ForkJoinPool.commonPool-worker-9
任务B, ForkJoinPool.commonPool-worker-9
获取到结果:null
获取到异常, java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
这里会被执行
执行结果, -1

Process finished with exit code 0
2.2.9 allOf,anyOf

allOf的方式是让内部编写多个CompletableFuture的任务,多个任务都执行完后,才会继续执行你后续拼接的任务

allOf返回的CompletableFutureVoid,没有返回结果

public static void main(String[] args) throws IOException {
    long start = System.currentTimeMillis();
    CompletableFuture<Void> task = CompletableFuture.allOf(
            CompletableFuture.runAsync(() -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务A");
            }),
            CompletableFuture.runAsync(() -> {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务B");
            }),
            CompletableFuture.runAsync(() -> {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务C");
            })
    ).thenRun(() -> {
        System.out.println("任务D");
    });
    System.out.println(task.join()); //阻塞主线程等待task任务执行完毕,便于查看执行过程
    System.out.println("执行时长:" + (System.currentTimeMillis() - start) + "ms");
}

console

任务A
任务B
任务C
任务D
null
执行时长:3037ms

Process finished with exit code 0

anyOf是基于多个CompletableFuture的任务,只要有一个任务执行完毕就继续执行后续,最先执行完的任务做作为返回结果的入参

public static void main(String[] args) throws IOException {
	long start = System.currentTimeMillis();
    CompletableFuture<Void> task = CompletableFuture.anyOf(
            CompletableFuture.supplyAsync(() -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务A");
                return "A";
            }),
            CompletableFuture.supplyAsync(() -> {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务B");
                return "B";
            }),
            CompletableFuture.supplyAsync(() -> {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务C");
                return "C";
            })
    ).thenAccept(r -> {
        System.out.println("任务D执行," + r + "先执行完毕的");
    });
    System.out.println(task.join()); //阻塞主线程等待task任务执行完毕,便于查看执行过程
    System.out.println("执行时长:" + (System.currentTimeMillis() - start) + "ms");
}

console

任务A
任务D执行,A先执行完毕的
null
执行时长:1050ms

Process finished with exit code 0

你可能感兴趣的:(java,多线程,java)