有三种 submit。这三种按照提交任务的类型来算分为两个类型。
提交执行 Runnable 类型的任务
提交执行 Callable 类型的任务
他们都是返回Future类型
看一下 Callable 类型的任务是怎么回事;
注意这个方法,一会会对比到;
接下来再说说 submit 的任务为 Runable 类型的情况
① 的方法扔进去一个 Runable 的任务,返回一个 Future,而这个返回的 Future ,相当于是返回了一个null。
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
Future> future = executor.submit(() -> {
System.out.println("=============");
});
System.out.println("future的内容:" + future.get());
Thread.currentThread().join();
// future.get() 方法的返回值为 null
future.get() 方法的返回值为 null
标号为 ② 的方法扔进去一个 Runable 的任务的同时,再扔进去一个泛型 T ,而巧好返回的 Future 里面的泛型也是 T,那么我们大胆的猜测一下这就是同一个对象。如果是同一个对象,说明我们可以一个对象传到任务体里面去一顿操作,然后通过 Future 再次拿到这个对象的。
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
AtomicInteger atomicInteger = new AtomicInteger();
Future future = executor.submit(() -> {
System.out.println("===========");
//在这里进行计算逻辑
atomicInteger.set(123456);
}, atomicInteger);
System.out.println("future的内容:" + future.get());
Thread.currentThread().join();
// future.get() 结果 123456
综上,线程池的提交方式一共有四种:一种 execute,无返回值。三种 submit,有返回值。
submit 中按照提交任务的类型又分为两种:一个是 Callable,一个是 Runable。
submit 中 Runable 的任务类型又有两个重载方法:一个返回了null,一个返回了个对象。
也可以说只有 execute 这一种提交方式
最后还有几种很冷门的提交:
从上面的代码我们可以看出,当我们想要返回值的时候,都需要调用下面的这个 get() 方法:
而从这个方法的描述可以看出,这是一个阻塞方法。拿不到值就在那里等着。当然,还有一个带超时时间的 get 方法,等指定时间后就不等了
总之就是有可能要等的。只要等,那么就是阻塞。只要是阻塞,就是一个假异步。
所以总结一下这种场景下返回的 Future 的不足之处:
只有主动调用 get 方法去获取值,但是有可能值还没准备好,就阻塞等待。
任务处理过程中出现异常会把异常隐藏,封装到 Future 里面去,只有调用 get 方法的时候才知道异常了。
或者你采用带超时时间的 future.get(timeout,unit) 方法
接下来,Guava 的 Future登场:
新增了一个 addListenter 方法,入参是一个 Runnable 的任务类型和一个线程池
ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
ListenableFuture listenableFuture = executor.submit(() -> {
System.out.println(Thread.currentThread().getName()+"-准备好,叫我");
TimeUnit.SECONDS.sleep(5);
return "任务完成了。";
});
listenableFuture.addListener(() -> {
try {
System.out.println(Thread.currentThread().getName()+"-future的内容:" + listenableFuture.get());
} catch (Exception e) {
e.printStackTrace();
}
}, executor);
System.out.println(Thread.currentThread().getName()+"-等任务结束之前,我可以干点别的");
Thread.currentThread().join();
首先创建线程池的方式变了,需要用 Guava 里面的 MoreExecutors 方法装饰一下
ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
然后用装饰后的 executor 调用 submit 方法(任意一种),就会返回 ListenableFuture ,拿到这个 ListenableFuture 之后,我们就可以在上面注册监听:
所以,上面的程序我们调用的是入参为 callable 类型的接口
获取运行结果是在另外的线程里面执行的,完全没有阻塞主线程。
除了上面的 addListener 方法外,另外还有 FutureCallback 的方式
ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
ListenableFuture listenableFuture = executor.submit(() -> {
System.out.println(Thread.currentThread().getName()+"-等任务完成叫我");
TimeUnit.SECONDS.sleep(5);
// throw new Exception("男神约我看电影,就不和你吃饭了。");
return "完成了";
});
Futures.addCallback(listenableFuture, new FutureCallback() {
@Override
public void onSuccess(@Nullable String result) {
System.out.println(Thread.currentThread().getName()+"-future的内容:" + result);
}
@Override
public void onFailure(Throwable t) {
System.out.println(Thread.currentThread().getName()+"-有情况,没完成");
t.printStackTrace();
}
});
System.out.println(Thread.currentThread().getName()+"-等你的时候,干点别的");
Thread.currentThread().join();
最后华丽登场的:
第一小节讲的 Future 是 JDK 1.5 时代的产物:
经过了这么多年的发展,Doug Lea 在 JDK 1.8 里面引入了新的 CompletableFuture :
到了 JDK 1.8 时代,这才是真正的异步编程。
CompletableFuture 实现了两个接口,一个是我们熟悉的 Future ,一个是 CompletionStage。
CompletionStage接口,你看这个接口的名称中有一个 Stage :
可以把这个接口理解为一个任务的某个阶段。所以多个 CompletionStage 链接在一起就是一个任务链。前一个任务完成后,下一个任务就会自动触发。
CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "-等任务完成了,叫我");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "任务完成了";
});
completableFuture.whenComplete((returnStr, exception) -> {
if (exception == null) {
System.out.println(Thread.currentThread().getName() + returnStr);
} else {
System.out.println(Thread.currentThread().getName() + "有情况,没完成");
exception.printStackTrace();
}
});
System.out.println(Thread.currentThread().getName() + "-等你的时候,干点别的");
Thread.currentThread().join();
我们执行的时候并没有指定用什么线程池,但是从结果可以看到也是异步的执行。
从输出日志中是可以看出端倪的,ForkJoinPool.commonPool() 是其默认使用的线程池。
也可以自己指定
这个方法在很多开源框架里面使用的还是非常的多的。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
接下来主要看看 CompletableFuture 对于异常的处理。
不需要 try-catch 代码块包裹,也不需要调用 Future.get() 才知道异常了,它提供了一个 handle 方法,可以处理上游异步任务中出现的异常:
CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "-任务完成好了我叫你。");
throw new RuntimeException("有情况,任务没完成,我告诉你一声。");
}).handleAsync((result, exception) -> {
if (exception != null) {
System.out.println(Thread.currentThread().getName() + "-有情况,没完成");
return exception.getCause();
} else {
return result;
}
}).thenApplyAsync((returnStr) -> {
System.out.println(Thread.currentThread().getName() + "-" + returnStr);
return returnStr;
});
System.out.println(Thread.currentThread().getName() + "-等干完的时候,忙点别的");
Thread.currentThread().join();