文章目录
Future接口理论知识复习
Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕。
只要是异步线程,比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动了子线程开始执行任务后,主线程就去做其他事情了,忙其他事情或者先执行完,过了一会去获取子任务的执行结果或变更的任务状态。
Future接口常用实现类FutureTask异步任务
Future接口能干什么
是JAVA5新加的一个接口,他提供了一种异步并行计算的功能。
如果主线程需要执行一个很耗时的计算任务,我们就可以通过future把这个任务放到异步线程中执行。
主线程继续处理其他任务或者先行结束,再通过Future获取计算结果。
相关架构
首先看一下Runnable接口和Callable接口
class MyThread1 implements Runnable{
@Override
public void run() {
}
}
class MyThread2 implements Callable<String>{
@Override
public String call() throws Exception {
return null;
}
}
Runnable接口有一个子类 -> RunnableFuture 其继承了Runnable和Future接口。所以它可以实现多线程和异步
RunnableFuture 又有一个子类FutureTask类,其可以传入Callable接口。
它实现了RunnableFuture 。
可以达到多线程、异步和有返回值。
编码实战和优缺点分析
测试:
运行Callable接口
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> stringFutureTask = new FutureTask<>(new MyThread1(),"结果");
new Thread(stringFutureTask).start();
String s = stringFutureTask.get();
System.out.println(s);//输出返回值
}
/**
* Runnable 接口
*/
static class MyThread1 implements Runnable{
@Override
public void run() {
System.out.println("runable 接口");
}
}
/**
* Callable接口
*/
static class MyThread2 implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("callable接口");
return "返回值";
}
}
}
输出结果
运行Runnable接口:
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> stringFutureTask = new FutureTask<>(new MyThread1(),"结果");
new Thread(stringFutureTask).start();
String s = stringFutureTask.get();
System.out.println(s);//输出返回值
}
结果:
优点:
future+线程池异步多线程任务配合,能显著提高程序的执行效率
缺点:
get()阻塞,因为需要得到返回值。
一但调用get()方法求结果,如果计算没有完成容易导致程序阻塞。
public class FutureThreadPoolDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(()->{
System.out.println(Thread.currentThread().getName() + "/t----开始");
TimeUnit.SECONDS.sleep(5);
return "运行结束";
});
Thread t1 = new Thread(futureTask,"t1");
t1.start();
String s = futureTask.get();
System.out.println(s);
System.out.println("main\t 结束");
}
}
结果:
因为调用了get()方法,所以会一直等待返回结果在继续运行。
也可以设置等待时间,如果到达时间没有得到返回结果,就会抛出异常。
String s = futureTask.get(3,TimeUnit.SECONDS);
可以捕获异常、进行下一步操作。
可以使用 isDone()方法 可以判断任务是否完成。
public class FutureThreadPoolDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
FutureTask<String> futureTask = new FutureTask<>(()->{
System.out.println(Thread.currentThread().getName() + "/t----开始");
TimeUnit.SECONDS.sleep(5);
return "运行结束";
});
Thread t1 = new Thread(futureTask,"t1");
t1.start();
while (true){
if (futureTask.isDone()){
System.out.println(futureTask.get());
break;
}else {
TimeUnit.MILLISECONDS.sleep(500);
System.out.println("正在处理");
}
}
}
}
结果:
会一直轮询、会耗费CPU资源。
如果想要异步获取结果,通常都会以轮询的方式去获取结果尽量不要阻塞
Future对结果的获取都不是很友好
CompletableFuture对Future的改进
CompletableFuture为什么出现
get()方法容易阻塞,isDome()方法容易消耗CPU资源
CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。
CompletableFuture和CompletionStage源码分别介绍
CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段。
一个阶段的执行可能是被单一阶段的完成触发,也可能是由多个阶段一起触发
有点类似管道通信
CompletableFuture 提供了非常强大的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方法处理计算结果。
他可能代表一个完成的阶段,也可能代表一个阶段。
核心的四个静态方法、来创建一个异步线程
可以使用构造方法创建一个对象
CompletableFuture<String> stringCompletableFuture = new CompletableFuture<>();
但是不推荐使用无参构造获得。(API说创建一个未完成的对象)
可通过下面的方式来创建对象
- runAsync 无返回值
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable,Executorexecutor)
如果没有指定线程池,则使用默认的ForkJoinPool.commonPool作为默认线程池。
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Void> CompletableFutureTest = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
System.out.println(CompletableFutureTest.get());
}
}
如传入一个线程池
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Executor executorService = Executors.newSingleThreadExecutor();
CompletableFuture<Void> CompletableFutureTest = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},executorService);
System.out.println(CompletableFutureTest.get());
}
}
- supplyAsunc 有返回值
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)
单参,即不传入线程池
CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "hello supplyAsync";
});
System.out.println(stringCompletableFuture.get());
传入线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "hello supplyAsync";
},executorService);
System.out.println(stringCompletableFuture.get());
通用功能,如何减少阻塞和轮询
从Java8开始引入了CompletableFure,他是Fature的功能增强办,减少轮询和阻塞,可以传入回调参数,当异步任务完成或者发生异常时,自动调用回调对象的回调功能。
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
int result = ThreadLocalRandom.current().nextInt(10);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("1s后结果为" + result);
return result;
});
System.out.println(Thread.currentThread().getName()+"主线程取完成其他任务");
System.out.println(completableFuture.get());
完全可以取代Futrue接口
两个参数。第一个参数是消费对象,第二个是异常对象
如以下程序
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
int result = ThreadLocalRandom.current().nextInt(10);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("1s后结果为" + result);
return result;
}).whenComplete((v,e)->{
if (e == null){
System.out.println("计算完成"+v);
}
}).exceptionally(e->{
e.printStackTrace();
return null;
});
System.out.println(Thread.currentThread().getName()+"主线程取完成其他任务");
}
结果:
并没有输出结果,这是为什么?
这是因为默认线程池就相当于一个守护线程,main方法结束了就死亡了。所以可以让主线程等待几秒。也可以传入一个线程池。
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
try {
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
int result = ThreadLocalRandom.current().nextInt(10);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("1s后结果为" + result);
return result;
},executorService).whenComplete((v,e)->{
if (e == null){
System.out.println("计算完成"+v);
}
}).exceptionally(e->{
e.printStackTrace();
return null;
});
System.out.println(Thread.currentThread().getName()+"主线程取完成其他任务");
}catch (Exception e){
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
结果:
CompletableFuture的优点:
- 异步任务结束时,会自动回调某个对象的方法
- 主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行
- 异步任务出错时,会自动回