一、概述
在业务需求中,很多时候异步执行某个任务之后是需要获取到执行结果的,但是使用继承Thread类和实现Runnable接口的方法来异步执行任务时,想要得到执行结果往往都不是很方便。那么除了这两种方式可以用来执行异步任务之外,还有一种方式可是实现,并且还可以简单的就获取到异步执行的结果,那就是Callable。
在使用Callable的同时一般都会使用Future来配合获取执行结果。
二、使用
现在有一个简单的业务需求:根据条件去多个服务器上获取资源,然后把资源放到一起,比如说根据图片编号去10台服务器上取出与该编号相关的10张图片
1,使用Runnable来实现
实现:创建10个线程,每个线程去一台服务器上获取图片资源,然后把获取到的结果方法一个线程安全的集合中,等待10个线程执行完成,就可以从这个集合中取出想要的结果
代码:
import java.util.*;
/**
* 使用runnable异步获取多个资源
*/
public class SearchImageDemo1 {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
String key = "image";
int searchCount = 10;// 10个资源存放点
// 用于存放线程
List<Thread> threadList = new ArrayList<>();
// 用于存放查询结果
List<String> resList = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < searchCount; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
// 查询并返回结果,把结果放入集合中
String data = getData(key);
resList.add(data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread-"+i);
thread.start();
threadList.add(thread);
}
// 等待10个线程执行结束后程序才继续执行
for (Thread thread : threadList) {
thread.join();
}
long end = System.currentTimeMillis();
System.out.println("总耗时:"+(end-start)+"ms");
System.out.println(resList.toString());// 输出查询结果
}
// 获取资源的方法
private static String getData(String str) throws InterruptedException {
long l = new Random().nextInt(3000);
Thread.sleep(l);// 模拟http请求耗时操作
System.out.println(Thread.currentThread().getName()+"耗时:"+l);
return str+":"+ UUID.randomUUID().toString();
}
}
2,使用Callable和FutureTask实现
实现:FutureTask是属于Future的一个实现类,同时它来实现了Runnable接口,所以可以把FutureTask放到Thread中执行。
代码:
import java.util.*;
import java.util.concurrent.*;
/**
* 使用Callable和FutureTask实现
*/
public class SearchImageDemo2 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
long start = System.currentTimeMillis();
String key = "image";
int searchCount = 10;// 10个资源存放点
// 用于存放Future
List<FutureTask> futureTaskList = new ArrayList<>();
// 用于存放查询结果
List<String> resList = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < searchCount; i++) {
FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
return getData(key);
}
});
futureTaskList.add(futureTask);
Thread thread = new Thread(futureTask,"Thread-"+i);
thread.start();
}
// 收集10个线程执行的结果,并把结果放入到集合中
for (FutureTask<String> futureTask : futureTaskList) {
String res = futureTask.get();
resList.add(res);
}
long end = System.currentTimeMillis();
System.out.println("总耗时:"+(end-start)+"ms");
System.out.println(resList.toString());// 输出查询结果
}
// 获取资源的方法
private static String getData(String str) throws InterruptedException {
long l = new Random().nextInt(3000);
Thread.sleep(l);
System.out.println(Thread.currentThread().getName()+"耗时:"+l);
return str+":"+ UUID.randomUUID().toString();
}
}
3,使用线程池、Callable和Future实现
实现:使用线程池的submit()方法,该方法接收一个Callable实现类,把任务交由线程池来执行,然后从Future中获取执行结果。
代码:
import java.util.*;
import java.util.concurrent.*;
/**
* 使用线程池、Callable和Future实现
*/
public class SearchImageDemo3 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
long start = System.currentTimeMillis();
String key = "image";
int searchCount = 10;// 10个资源存放点
// 用于存放线程
List<Future> futureTaskList = new ArrayList<>();
// 用于存放查询结果
List<String> resList = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < searchCount; i++) {
Future<String> future = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return getData(key);
}
});
futureTaskList.add(future);
}
// 收集10个线程执行的结果,并把结果放入到集合中
for (Future<String> future : futureTaskList) {
String res = future.get();
resList.add(res);
}
long end = System.currentTimeMillis();
System.out.println("总耗时:"+(end-start)+"ms");
System.out.println(resList.toString());// 输出查询结果
executorService.shutdown();// 关闭线程池
}
// 获取资源的方法
private static String getData(String str) throws InterruptedException {
long l = new Random().nextInt(3000);
Thread.sleep(l);
System.out.println(Thread.currentThread().getName()+"耗时:"+l);
return str+":"+ UUID.randomUUID().toString();
}
}
三、Future的方法
get():会阻塞线程,直到任务执行完毕
get(long timeout,TimeUnit unit):可以设置等待超时时间,如果在指定时间内任务还没有返回结果,那么取消阻塞等待。
cancel(boolean mayInterruptIfRunning):方法可以用来停止一个任务,如果任务可以停止(通过mayInterruptIfRunning来进行判断),则可以返回true,如果任务已经完成或者已经停止,或者这个任务无法停止,则会返回false.
isDone():判断当前方法是否完成
isCancel():判断当前方法是否取消
四、使用CompletableFuture
在jdk1.8中,提供了一个异步处理任务的工具
1,使用CompletableFuture实现上面的需求
import java.util.*;
import java.util.concurrent.*;
/**
* 使用CompletableFuture实现
*/
public class SearchImageDemo4 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
long start = System.currentTimeMillis();
String key = "image";
int searchCount = 10;// 10个资源存放点
// 用于执行结果
CompletableFuture<String>[] futures = new CompletableFuture[searchCount];
// 用于存放查询结果
List<String> resList = Collections.synchronizedList(new ArrayList<>());
// 启动10个任务异步执行
for (int i=0;i<searchCount;i++) {
futures[i] = CompletableFuture.supplyAsync(() -> {
try {
return getData(key);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
});
}
// 获取这些执行结果
// Stream.of(futures).forEach((f)->{f.whenComplete((v,e)->{resList.add(v);}).join();});
for (CompletableFuture<String> future : futures) {
future.whenComplete((v,e)->{
resList.add(v);
}).join();
}
long end = System.currentTimeMillis();
System.out.println("总耗时:"+(end-start)+"ms");
System.out.println(resList.toString());// 输出查询结果
}
// 获取资源的方法
private static String getData(String str) throws InterruptedException {
long l = new Random().nextInt(3000);
Thread.sleep(l);
System.out.println(Thread.currentThread().getName()+"耗时:"+l);
return str+":"+ UUID.randomUUID().toString();
}
}
2,CompletableFuture介绍
2.1 创建一个CompletableFuture异步操作对象
有两个方法:
runAsync():接收一个Runnable对象,执行之后没有返回值。重载方法可以指定线程池,如果不传则默认使用ForkJoinPool.commonPool()作为线程池。
supplyAsync():接受Supplier对象,执行之后有返回值。重载方法可以指定线程池,如果不传则默认使用ForkJoinPool.commonPool()作为线程池。
2.2 获取执行结果
2.2.1 同步获取结果
使用get()或者join()方法获取结果
2.2.2 任务完成之后处理结果
whenComplete():使用当前执行任务的线程处理结果
whenCompleteAsync():使用线程池中的线程处理结果
exceptionally():返回一个新的CompletableFuture,它在CompletableFuture完成时完成,当它异常完成时,给定的异常函数会触发;如果这个CompletableFuture正常完成,那么返回的CompletableFuture也以相同的值正常完成。
handle():在任务执行完成后对结果进行处理,如果执行过程中发生异常,还可以对异常进行捕获并处理。