一、Future接口理论知识
1、概述
Future接口可以为主线程开启一个分支任务,专门为主线程处理耗时和费力的复杂业务。
- Future接口定义了操作异步任务执行的一些方法,如获取异步任务的执行结果,取消任务的执行,判断任务是否被取消,判断任务是否执行完毕。
- 比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事了,忙其他事情或者先执行完,过了一会才去获取子任务的执行结果或变更的任务状态。
2、FutureTask
Future接口常用实现类FutureTask来实现异步任务
- Future接口是java5新加的一个接口,它提供了一种异步并行计算的功能。
- 如果主线程需要执行一个很耗时的计算任务,我们就可以通过future把这个任务放到异步线程中去执行。
- 主线程继续处理其他任务或者先行结束,再通过Future获取计算结果 目的:异步多线程任务执行且有返回结果,三个特点:多线程/有返回/异步任务
要想有一个多线程任务,必须new一个Thread,而Thread()方法中只能传入Runable类型的参数,但异步多线程任务三个特点中又要求有返回,而Runable中又没有返回。所以需有一个同事满足三个特点的方法。
- 获取一个带有返回的异步多线程的例子
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***");
}
}
开启多线程
- 同样三个任务,两个放到线程池里面,一个作为主线程,如下,获取开启线程中的返回结果要放到主线程下面,否则会阻塞主线程
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)+" 毫秒");
}
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());
}
}
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("主线程要去忙其他事情啦*************");
}
}
- 获取线程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("主线程要去忙其他事情啦*************");
}
}
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("不要再催了,拼命拉磨呢~~~~~~~~~~~~~~");
}
}
}
}
- 轮询的方式会耗费cpu资源,而且也不见得能及时的得到结果
- 如果想要异步获取结果,通常都会用轮询的方式获取结果,尽量不要阻塞
Future对于结果的获取并不是很友好,只能通过阻塞,或者轮询得到任务的结果
二、CompletableFuture
1、CompletableFuture为什么会出现
- get()方法在Future计算完成之前会一直处于阻塞状态
- isDone()方法容易耗费CPU资源
- 对于真正的异步处理,我们希望可以通过传入回调函数,在Future结束时自动调用该回调函数,这样,我们就不用等待结果
- 阻塞的方式和异步编程相违背,而轮询方式会耗费无畏的cpu资源,因此jdk8设计出了CompletableFuture
- CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完后通知监听的一方
2、源码简介
1、类架构说明
- 在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()});
- 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发。