在执行一些需要耗时处理的任务时,希望通过多线程计算,然后异步把各个结果返回,进行合并处理,在Java5可以通过Java线程池配合Future接口实现。
Future Api文档的介绍
-
public interface Future<V>一个
Future表示异步计算的结果。提供的方法来检查,如果计算完成,等待其完成,并检索结果的计算。结果只能检索使用方法get当计算完成后,如果有必要,直到阻塞。取消由cancel方法进行。提供了额外的方法来确定任务是否正常完成或被取消。一旦计算完成,计算不能被取消。如果你想使用一个Future为可的缘故,但不提供一个可用的结果,你可以声明的形式Future<?>回null类型作为结果的基本任务。只有几个方法,比较简单:
| 返回值 | 方法 |
|---|---|
| boolean | cancel(boolean mayInterruptIfRunning) 试图取消此任务的执行。 |
V | get() 等待,如果需要计算完成,然后检索其结果。 |
V | get(long timeout, TimeUnit unit) 如果需要的话,在大多数给定的计算时间完成,然后检索其结果,如果可用。 |
boolean | isCancelled() 返回 true如果这个任务完成之前取消正常。 |
boolean | isDone() 返回 true如果完成这个任务。 |
举一个例子
创建两个任务
public class Task2 implements Callable<String> {
@Override
public String call() throws Exception {
/**
* 计算后返回一个字符串,耗时3秒
*/
Thread.sleep(3000);
return "world";
}
}
public class Task1 implements Callable<String> {
@Override
public String call() throws Exception {
/**
* 计算后返回一个字符串,耗时1秒
*/
Thread.sleep(1000);
return "hello";
}
}
创建一个方法执行上面两个任务,并合并处理结果
/**
* 处理任务,合并结果
*
* @return
* @throws ExecutionException
* @throws InterruptedException
*/
public static String test() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ExecutorService executorService = Executors.newFixedThreadPool(5);
/**
*
* sumbmit传入的参数Callable也是函数式接口,也可以写成:
*
* Future<String> task1 = executorService.submit(() -> {
* Thread.sleep(1000);
* return "hello";
* });
*
*/
Future<String> task1 = executorService.submit(new Task1());
Future<String> task2 = executorService.submit(new Task2());
/**
* 处理其他任务,返回结果
*/
Thread.sleep(3000);
String s3 = ", I'm coming";
String s = task1.get();
String s1 = task2.get();
long end = System.currentTimeMillis();
System.out.println("总耗时:" + (end - start) / 1000);
executorService.shutdown();
return s + " " + s1 + s3;
}
执行方法,发现整个方法耗时共3秒,在Future中已经触发了耗时的两个操作,把调用线程解放出来,让它能继续执行其他有价值的工作,不再需要呆呆等待耗时的操作完成
public static void main(String[] args) throws ExecutionException, InterruptedException {
String result = test();
System.out.println(result);
// 总耗时:3
// hello world, I'm coming
}
Future接口的局限性
Future接口虽然提供了方法来进行异步操作,让调用线程可以处理其他任务,以及等待任务完成,获取接口计算结果,但是这些特性并不足以编写简洁的代码和满足日常的开发需求,比如
-
比如几个异步计算合并为一个,而且这个几个异步计算有先后问题
-
等待所有异步计算的任务完成进行处理
-
仅处理最先完成的任务(比如调用两个相同的第三方API,哪个API耗时少调用哪个,减少接口耗时)
CompletableFuture 使用
在上述说Future接口的几个局限性,我们先通过几个实例来说明CompletableFuture如何弥补这几种局限性
1、先后顺序
CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> {
/**
* 计算返回一个字符串
*/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello";
}).thenCombine(CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "world";
}), (x, y) -> {
return x + y;
});
System.out.println("s:"+stringCompletableFuture.join());
/**
* s:helloworld
*/
2、仅处理最先完成的任务
CompletableFuture<String> sCf = CompletableFuture.supplyAsync(() -> {
/**
* 计算返回一个字符串
*/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "a";
}).applyToEitherAsync(CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "b";
}), (x) -> {
return x;
});
System.out.println("s:"+sCf.join());
/**
* s:a
*/
或者
CompletableFuture<String> sCf = CompletableFuture.supplyAsync(() -> {
/**
* 计算返回一个字符串
*/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "a";
});
CompletableFuture<String> sCf2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "b";
});
CompletableFuture<Object> objectCompletableFuture = CompletableFuture.anyOf(sCf, sCf2);
String join = (String) objectCompletableFuture.join();
3、等待所有异步计算的任务完成进行处理
long start = System.currentTimeMillis();
CompletableFuture<String> sCf = CompletableFuture.supplyAsync(() -> {
/**
* 计算返回一个字符串
*/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "a";
});
CompletableFuture<String> sCf2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "b";
});
CompletableFuture<String> sCf3= CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "c";
});
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(sCf, sCf2,sCf3);
voidCompletableFuture.join();
long end = System.currentTimeMillis();
System.out.println("总耗时:" + (end - start) / 1000);
System.out.println();
ArrayList<CompletableFuture<String>> cfList = new ArrayList<>();
cfList.add(sCf);
cfList.add(sCf2);
cfList.add(sCf3);
cfList.stream().map(CompletableFuture::join).forEach(System.out::println);
/**
* 总耗时:5
* a
* b
* c
*/
4、配合线程池处理
CompletableFuture 默认使用线程池ForkJoin池来实现的(线程池中的处理线程数=电脑核数-1,如果生产环境服务器核心数太小,等有大请求量过来,处理逻辑又很复杂,很多线程都在等待执行,会慢慢拖垮了服务器),所以我们使用自定义线程池实现,比如
ExecutorService executorService = Executors.newFixedThreadPool(5);
CompletableFuture<String> sCf3= CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "c";
},executorService);
5、异常处理
CompletableFuture 提供了很多方法满足平时异步计算各种需求,有些方法后缀会带上Async,表示该方法的处理还是给线程池处理,不带则表示由调用线程处理;还提供将异常处理抛出,得到线程内具体的异常提示信息
ExecutorService executorService = Executors.newFixedThreadPool(5);
CompletableFuture<String> sCf3 = CompletableFuture.supplyAsync(() -> {
int i = 1 / 0;
return "c";
}, executorService).exceptionally(e -> {
e.printStackTrace();
return"1";
});
String str = sCf3.join();
System.out.println("str:"+str);
executorService.shutdown();