0
点赞
收藏
分享

微信扫一扫

二、JUC-CompletableFuture

一、Future接口理论知识

1、概述

Future接口可以为主线程开启一个分支任务,专门为主线程处理耗时和费力的复杂业务。

  • Future接口定义了操作异步任务执行的一些方法,如获取异步任务的执行结果,取消任务的执行,判断任务是否被取消,判断任务是否执行完毕。
  • 比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事了,忙其他事情或者先执行完,过了一会才去获取子任务的执行结果或变更的任务状态。 12.png

2、FutureTask

Future接口常用实现类FutureTask来实现异步任务

  • Future接口是java5新加的一个接口,它提供了一种异步并行计算的功能。
  • 如果主线程需要执行一个很耗时的计算任务,我们就可以通过future把这个任务放到异步线程中去执行。
  • 主线程继续处理其他任务或者先行结束,再通过Future获取计算结果 目的:异步多线程任务执行且有返回结果,三个特点:多线程/有返回/异步任务

要想有一个多线程任务,必须new一个Thread,而Thread()方法中只能传入Runable类型的参数,但异步多线程任务三个特点中又要求有返回,而Runable中又没有返回。所以需有一个同事满足三个特点的方法。 13.png 14.png

  • 获取一个带有返回的异步多线程的例子
package com.lori.juc2023.juc1;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CompletableFutureDemo {
    public static void main(String[] args) {
        FutureTask<String> futureTask = new FutureTask<>(new MyThread2());
//异步任务
        Thread thread = new Thread(futureTask,"t1");
        thread.start();
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }
}




class MyThread2 implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("**************Callable******************");
        return "hello";
    }
}
  • 返回结果
**************Callable******************
hello

Process finished with exit code 0

3、优点

future+线程池异步多线程任务配合,能够显著的提升执行效率

  • 不使用多线程执行这一段方法,大概就是每个线程时间相加之和
package com.lori.juc2023.juc1;

import java.util.concurrent.TimeUnit;

public class FutureThreadPoolDemo {
    public static void main(String[] args) {
        //3个任务目前只有一个线程处理,耗时多少?
        long start = System.currentTimeMillis();
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        long end = System.currentTimeMillis();


        System.out.println("********耗时:::"+(end-start)+" 毫秒");

        System.out.println(Thread.currentThread().getName()+"\t"+"***end***");
    }
}

15.png 开启多线程

  • 同样三个任务,两个放到线程池里面,一个作为主线程,如下,获取开启线程中的返回结果要放到主线程下面,否则会阻塞主线程
    public static void main(String[] args) {

        //3个任务,开启多个异步任务,耗时多少?
        //如果线程多了,每次去new一个线程,涉及到垃圾回收,会使程序慢慢变得臃肿,所以我们来创建一个线程池
        ExecutorService service = Executors.newFixedThreadPool(3);
        long start = System.currentTimeMillis();
        FutureTask<String> futureTask = new FutureTask<String>(
                ()->{
                    try {
                        TimeUnit.MILLISECONDS.sleep(500);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return "task1 over";
                });
      service.submit(futureTask);

        FutureTask<String> futureTask2 = new FutureTask<String>(
                ()->{
                    try {
                        TimeUnit.MILLISECONDS.sleep(300);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return "task2 over";
                });

        service.submit(futureTask2);

        service.shutdown();
    //main线程
                    try {
                        TimeUnit.MILLISECONDS.sleep(300);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

        try {
            System.out.println(futureTask.get());
            System.out.println(futureTask2.get());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }

        long end = System.currentTimeMillis();
        System.out.println("********耗时:::"+(end-start)+" 毫秒");
    }

16.png

4、缺点

1、缺点一

  • get()容易导致阻塞,一旦调用get(),要等待线程执行完成才会返回,所以一般把get()放到主线程后面执行,不耽误主线程的执行。
package com.lori.juc2023.juc1;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

public class FutureAPIDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<String>(()->{
                System.out.println("开启线程11111");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "task1 over";
        });
        Thread thread = new Thread(futureTask, "t1");
         thread.start();
        System.out.println("主线程要去忙其他事情啦*************");
        System.out.println(futureTask.get());
    }
}

17.png

package com.lori.juc2023.juc1;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

public class FutureAPIDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<String>(()->{
            try {
                System.out.println("开启线程11111");
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "task1 over";
        });
        Thread thread = new Thread(futureTask, "t1");
        thread.start();
        System.out.println(futureTask.get());
        System.out.println("主线程要去忙其他事情啦*************");
  
    }
}

18.png

  • 获取线程1的结果,不管1线程需要多久执行完成,我只等1s,过时不候,但是会抛出异常
package com.lori.juc2023.juc1;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class FutureAPIDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<String>(()->{
            try {
                System.out.println("开启线程11111");
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "task1 over";
        });
        Thread thread = new Thread(futureTask, "t1");
        thread.start();
        //获取线程1的结果,不管1线程需要多久执行完成,我只等1s,过时不候
        try {
            System.out.println(futureTask.get(1,TimeUnit.SECONDS));
        } catch (TimeoutException e) {
            throw new RuntimeException(e);
        }
        System.out.println("主线程要去忙其他事情啦*************");

    }
}

19.png

2、缺点二

isDone()导致耗费更多的系统资源

  • 判断线程1执行完没有,执行完了就获取结果,没执行完继续等待
package com.lori.juc2023.juc1;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class FutureAPIDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<String>(()->{
            try {
                System.out.println("开启线程11111");
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "task1 over";
        });
        Thread thread = new Thread(futureTask, "t1");
        thread.start();
        System.out.println("主线程要去忙其他事情啦*************");
          //判断线程1执行完没有,执行完了就获取结果,没执行完继续等待
        while (true){
            if(futureTask.isDone()) {
                System.out.println(futureTask.get());
                break;
            }else {
                TimeUnit.SECONDS.sleep(1);
                System.out.println("不要再催了,拼命拉磨呢~~~~~~~~~~~~~~");
            }
        }



    }
}

20.png

  • 轮询的方式会耗费cpu资源,而且也不见得能及时的得到结果
  • 如果想要异步获取结果,通常都会用轮询的方式获取结果,尽量不要阻塞

Future对于结果的获取并不是很友好,只能通过阻塞,或者轮询得到任务的结果

二、CompletableFuture

1、CompletableFuture为什么会出现

  • get()方法在Future计算完成之前会一直处于阻塞状态
  • isDone()方法容易耗费CPU资源
  • 对于真正的异步处理,我们希望可以通过传入回调函数,在Future结束时自动调用该回调函数,这样,我们就不用等待结果
  • 阻塞的方式和异步编程相违背,而轮询方式会耗费无畏的cpu资源,因此jdk8设计出了CompletableFuture
  • CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完后通知监听的一方

2、源码简介

1、类架构说明

21.png 22.png

  • 在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合CompletableFuture的方法
  • 它可能代表一个明确完成的Future,也有可能代表一个完成阶段(CompletionStage),它支持在计算完成以后触发一些函数或执行某些动作
  • 它实现了Future和CompletionStage接口

2、接口CompletionStage<T>

  • CompletionStage代表异步计算中的某一个阶段,一个阶段完成后可能会触发另一个阶段
  • 一个阶段的计算结果可以是Future、Consumer、Runnable。比如:stage.thenApply (x->square(x)).thenAccept(x->System.out.println(x)).thenRun(()->{System.out.println()});
  • 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发。
举报

相关推荐

0 条评论