大家好,我是栗子为。
“又有几天没和大家见面了,小为最近也是刚回到家,调整状态做好打工人的准备。虽然毕业了都说好好放松一下,但小为回到家每天都保证一定的学习时间,真的不是想做那个最卷的人,主要为了你们能学到更多(毫不夸张!)。
这个阶段呢我想带着大家学一下更深入的Java并发编程系列,从JUC(java.util.concurrent)包展开介绍,帮助大家在面试中轻松拿下这些高并发的问题。”
之前有介绍过Java多线程的相关知识,可以看Java多线程那些事(一)这篇文章,如果异步执行任务的时候需要有返回值,我们可以通过实现Callable接口,调用Executor的submit方法,再使用Future获取即可。当多个线程存在依赖组合的情况,我们能想到CountDownLatch、CyclicBarrier等,其实利用CompletableFuture就可以解决,这一次我们就来聊聊CompletableFuture。
话不多说,跟着小为开始今天的学习叭!
01
—
什么是Future接口?
Future接口(常用的实现类FutureTask)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。
有如下的方法
-
cancel() -
get() -
get(long, TimeUnit) -
isCancelled() -
isDone()
举个🌰
主线程让子线程去执行任务,此时主线程做自己的任务,过段时间再去获取子任务执行的结果
02
—
FutureTask实现类
三个特征:多线程
、有返回
、异步任务
构造方法:
-
FutureTask(Callable
callable) -
FutureTask(Runnable runnable,V result)
举个🌰
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class FutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new MyThread());
Thread thread = new Thread(futureTask, "t1");
thread.start();
System.out.println(futureTask.get()); // 通过此方法拿到任务返回结果:call返回
}
}
class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("进入call方法");
return "call返回";
}
}
存在的问题
1.get()方法会阻塞
看如下代码(只看主函数)
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + "t开始执行");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "task over";
});
Thread t1 = new Thread(futureTask, "t1");
t1.start();
System.out.println(Thread.currentThread().getName() + "t忙其他任务");
System.out.println(futureTask.get());
}
运行结果如下
main 忙其他任务
t1 开始执行
(等待五秒钟...)
task over
若将futureTask.get()
方法往上移,即
System.out.println(futureTask.get());
System.out.println(Thread.currentThread().getName() + "t忙其他任务");
运行结果如下
t1 开始执行
(等待五秒钟...)
task over
main 忙其他任务
可以发现get()
方法拿到结果后才会继续往下执行,会阻塞主线程
2.isDone()轮询耗费CPU资源
采用轮询的方式,不断询问任务是否完成,这样能避免等待,其核心思想如下
while (true) {
if (futureTask.isDone()) {
System.out.println(futureTask.get());
break;
} else {
try {
TimeUnit.MILLISECONDS.sleep(500);
System.out.println("请稍等正在处理中...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
缺点:
-
轮询的方式会耗费CPU资源,而且也不能及时得到任务结果
Future总结
Future对于结果的获取不是很友好,只能通过阻塞或轮询的方式得到任务结果 Future对于一些简单业务场景比较友好 可以利用Future和线程池的方式来创建异步任务 可以用任务完成回调代替轮询,提高系统效率 若多个异步任务的结果相互依赖,利用Future则不能完成
03
—
CompletableFuture
在Java 8中, 新增加了一个包含50个方法左右的类: CompletableFuture,结合了Future的优点,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。
CompletableFuture提供了一种观察者模式的机制,可以让任务执行完成后通知监听的一方
在源码中对这个类的定义:
public class CompletableFuture<T> implements Future<T>, CompletionStage<T>{
}
CompletionStage接口
代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段,有些类似Linux系统的管道分隔符
-
CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成后可能会触发另外一个阶段 -
一个阶段的计算执行可以是一个Function,Consumer或者Runnable -
一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发
四大静态方法
四大静态方法
注:
-
上述 Executor executor
参数,若没有指定Executor方法,直接使用默认的ForkJoinPool.commonPool()
作为它的线程池执行异步代码;如果指定线程池,则使用我们自定义的或者特别指定的线程池执行异步代码 -
Supplier 是一个功能接口,因此可以用作lambda表达式或方法引用的赋值对象。
因此针对于不同的场景,采用不同的静态方法。下面来看一些具体场景
举个🌰(不指定Executor方法)
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(completableFuture.get());
}
}
结果如下
ForkJoinPool.commonPool-worker-9
null
举个🌰(指定Executor方法)
import java.util.concurrent.*;
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(3); // 创建一个固定大小的线程池
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, threadPool);
System.out.println(completableFuture.get());
threadPool.shutdown();
}
}
结果如下
pool-1-thread-1
null
可以发现,当我们指定线程池后,使用的是我们线程池里的线程
举个🌰(有返回值)
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "方法返回";
});
System.out.println(completableFuture.get());
}
}
结果如下
ForkJoinPool.commonPool-worker-9
方法返回
04
—
总结
总结一下,并发编程包里的CompletableFuture类包含了Future方法的所有优点,也提供了更强大的API,解决了很多异步任务的场景,本篇主要介绍Future接口以及它的实现类FutureTask,让大家对异步编程,带返回值等有了初步的理解,下一讲将深入CompletableFuture类,看看它是如何解决FutureTask存在的阻塞以及轮询耗时等问题的。今天这篇文章写下来,小为还是觉得非常有必要,并发编程算是我们进入公司后一定要具备的技能,这其中有很多解决不同场景的类都需要我们去掌握,同时还能对以前学的知识进行复习和合并,所以大家一起加油哦!
关注六只栗子,面试不迷路!
作者 栗子为
编辑 一口栗子
原文始发于微信公众号(六只栗子):JUC之CompletableFuture(一)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/88589.html