项目中在处理多并非业务时需要用到多线程,这是我们一般都只是使用一些简单的异步线程来开发我们的需求。
比如说 new Thread().start,然后传入一个 实现了runable接口的实现类即可,这样可以满足一些简单的并发需求需要 。
但是这有一些复杂的需求往往需求线程去处理多步流程,比如说我需求拿到线程的执行结果,从而进行我的下一步计算的时候,这时候简单的使用 new Thread() 可满足不了
这时候就要用到 java.util.concurrent.* 这个java 高性能并发包了,里面提供不了一系列能够满足java多线程复杂场景下的多线程技术。
Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。(异步:可以被叫停,可以被取消)
** 一句话:Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力的复杂业务。**
图中我们可以看到,Future接口主要方法有5个:
1 cancel(boolean mayInterruptIfRunning) 方法可以用来停止一个任务,如果任务可以停止(通过mayInterruptIfRunning来进行判断),则可以返回true,如果任务已经完成或者已经停止,或者这个任务无法停止,则会返回false
2 isCancelled( 方法判断当前方法是否取消
3 get() 方法可以当任务结束后返回一个结果,如果调用时,工作还没有结束,则会阻塞线程,直到任务执行完毕
4 get(long timeout,TimeUnit unit) 做多等待timeout的时间就会返回结果
5 isDone() 方法判断当前方法是否完成
话不多说,直接上代码
//FutureTask简单使用
@Test
public void test0() throws ExecutionException, InterruptedException {
FutureTask<String>futureTask1=new FutureTask(()->{
String hello="你好,小北呱";
return hello;
});
new Thread(futureTask1).start();
String s = futureTask1.get();
System.out.println(s);
}
使用FutureTask步骤:
创建 FutureTask()类,然后写入自定义方法,定义好返回值类型,再丢入Thread()中,启动即可
其中 futureTask1.get() 方式适用获取执行完后的返回值的,这里的返回值是 你好,小北呱,然后再在控制台打印。
不过一般业务量大的使用,我们一般会搭配线程池使用,这样会节省不必要的资源花销:
//FutureTask配合线程数使用
@Test
public void test1() throws ExecutionException, InterruptedException, TimeoutException {
ExecutorService executorService= Executors.newFixedThreadPool(3);
FutureTask<String>futureTask1=new FutureTask(() -> {
Thread.sleep(1000);
String name=Thread.currentThread().getName();
return name;
});
FutureTask<String>futureTask2=new FutureTask(() -> {
Thread.sleep(3000);
String name=Thread.currentThread().getName();
return name;
});
FutureTask<String>futureTask3=new FutureTask(() -> {
Thread.sleep(1000);
String name=Thread.currentThread().getName();
return name;
});
executorService.submit(futureTask1);
executorService.submit(futureTask2);
executorService.submit(futureTask3);
List<Future> list=new ArrayList<>();
list.add(futureTask1);
list.add(futureTask2);
list.add(futureTask3);
while (true){
for (int i=0;i<list.size();i++){
if (list.get(i).isDone()){
System.out.println(list.get(i).get());
list.remove(i);
}
}
if (list.size()==0){
break;
}
}
}
先创建了一个线程池,然后再创建3个 FutureTask() 模拟多个并发任务,然后将他们丢入线程池执行即可,再创建一个list集合用于保存他们的执行结果,这里的结果是 Thread.currentThread().getName() ,也就是当前线程的线程名:
demo:
//FutureTask demo
@Test
public void test0() throws ExecutionException, InterruptedException {
ExecutorService executorService= Executors.newFixedThreadPool(3);
long start = System.currentTimeMillis();
FutureTask<String>futureTask1=new FutureTask(()->{
System.out.println("在准备包子。。。");
Thread.sleep(2000);
return "包子准备完毕";
});
FutureTask<String>futureTask2=new FutureTask(()->{
System.out.println("在准备凉菜。。。");
Thread.sleep(2000);
return "凉菜准备完毕";
});
FutureTask<String>futureTask3=new FutureTask(()->{
System.out.println("在准备豆浆。。。");
Thread.sleep(2000);
return "凉菜准备完毕";
});
executorService.submit(futureTask1);
executorService.submit(futureTask2);
executorService.submit(futureTask3);
List<Future> list=new ArrayList<>();
list.add(futureTask1);
list.add(futureTask2);
list.add(futureTask3);
while (true){
for (int i=0;i<list.size();i++){
if (list.get(i).isDone()){
System.out.println(list.get(i).get());
list.remove(i);
}
}
if (list.size()==0){
break;
}
}
long end = System.currentTimeMillis();
System.out.println("准备完毕时间:"+(end-start));
}
耗时我们可以看出,本来要6秒要做到的事情,2秒就结束了,而且还带回来了执行的结果,这就是 FutureTask 类比普通线程的优势
get()阻塞:
一旦调用get()方法,不管方法在另外执行什么,都会导致阻塞,导致方法停滞不前(所以一般get方法放到最后)
isDone()轮询:
利用if(futureTask.isDone())的方式使得他在结束之后才get(),但是也会消耗cpu,过度使用会消耗大量cpu资源
由于阻塞的方式和异步编程的设计理念相违背,而轮询的方式会消耗无畏的CPU资源。因此,JDK8设计出 CompletableFuture,它相对与futureTask来说更加智能,更加便捷。
好处:
1:解决异步线程阻塞取值从而影响执行效率的难题 2:解决以往轮询方式的获取值导致的CPU消耗问题 2:使用难度较以往方式降低,效率有所提升
- 1
- 2
- 3
创建 CompletableFuture 类官方不推荐直接使用 **new()**的方式,因为它只有一个无参构造,这样构造出来的类功能是不完整的,为此推荐使用 它自带的4个静态方法进行类的创建,从而得到不同功能的 CompletableFuture。
1、runAsync(Runnable runnable) 使用默认线程池创建(不带返回值)
2、runAsync(Runnable runnable,Executor executor) 使用自定义线程池创建(不带返回值)
——————————————————————————————————————————————
3、supplyAsync(Supplier supplier) 使用默认线程池创建(带返回值)
4、supplyAsync(Supplier supplier,Executor executor) 使用自定义线程池创建(带返回值)
例子1:
//不带返回值
@Test
public void test1() throws ExecutionException, InterruptedException {
ExecutorService executorService= Executors.newFixedThreadPool(2);
//无返回值方法
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(()->{
System.out.println(Thread.currentThread().getName());
System.out.println("hello");
},executorService);
voidCompletableFuture.get();
}
例子2:
@Test
public void test1() throws ExecutionException, InterruptedException {
ExecutorService executorService= Executors.newFixedThreadPool(2);
//带返回值方法
CompletableFuture<String> voidCompletableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName());
String s="你好";
return s;
},executorService);
System.out.println(voidCompletableFuture.get());
}
可以发现,上述的4个方法中基本可以完成前面提及的FutureTask的所有功能,但是这远远不够,开头我们聊到,我们需要用到多线程中上一步的返回结果用来进行下一步的计算时,就连FutureTask 也得吃闭门羹,因为使用上述的两种方法看来,我们还是需要去阻塞获取结果再进行下一步计算,这样是不明智的,所以请继续看demo:
@Test
public void test2() {
ExecutorService executorService= Executors.newFixedThreadPool(2);
CompletableFuture<String> objectCompletableFuture = CompletableFuture.supplyAsync(()->{
String s="大家好";
return s;
},executorService).whenComplete((v,e)->{
//判断是否出现异常
if (e==null){
String x="我是小北呱";
String name=v+x;
System.out.println(name);
}
}).exceptionally((e)->{
e.printStackTrace();
System.out.println("异常情况:"+e.getCause()+"\t"+e.getMessage());
return null;
});
}
重上述代码可以看出,在获取前一步值 (“大家好”) 时,我们不再使用get() 进行阻塞等待了,而是引入了whenComplete() 方法,它的作用就是当上一步执行完后,将结果和值传递给方法内的下一步进行进一步处理。这样就成功的避免了FutureTask类的不良问题,简直是完美
当然了CompletableFuture 中还新加入的很多方法,这只是个demo,后续会保持更新。。。。。