我们上一篇文章《线程池原理》主要学习了线程池的参数和提交任务的方法execute,本篇文章和大家一起学习提交任务的另一个方法submit。
在开发中经常会遇到一个任务的执行时间比较长,如果我们一直等待任务的返回结果将会使线程进入长时间的阻塞,JDK1.5之后引入了Future,并且在JDK1.8时引入了CompletableFuture解决了此问题。
Future介绍
Future代表着一个异步任务在未来的执行结果,这个结果可以在最终的某个时间通过Future提供的get方法获得,对于一个时间比较长的执行任务来说,用Future是比较合适的,任务在执行过程中线程可以先去做其他事情。
我们先来看看Future接口中定义了哪些方法:
public interface Future<V> {
//取消异步的任务
//如果mayInterruptIfRunning为true,表示可以打断正在运行的线程,则工作线程将会被打断
//如果mayInterruptIfRunning为false,正在运行的线程将不受影响继续执行任务
boolean cancel(boolean mayInterruptIfRunning);
//判断异步任务是否被取消
//不论mayInterruptIfRunning为true还是false,只要执行了cancel方法,isCancelled返回的结果都为true
boolean isCancelled();
//判断异步任务是否执行结束
boolean isDone();
//获取异步任务的执行结果
//如果任务还未执行结束,该方法会使当前线程阻塞
//如果任务运行错误,该方法会抛出ExecutionException异常
V get() throws InterruptedException, ExecutionException;
//获取异步任务的执行结果,可以设置线程阻塞的时间
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
ExecutorService与Future
线程池可以通过submit方法提交一个Callable类型的任务并且返回Future,而且还可以提交一个Runnable类型的任务。
//提交Callable类型的任务,在任务执行结束后可以通过get方法获取到结果
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
//构建FutureTask
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
//提交Runnable类型的任务并且返回Future,在执行执行结束后,通过get方法获取的结果为null
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
//提交Runnable类型的任务并且返回Future,在执行执行结束后,通过get方法获取不到结果,通过submit方法则可以
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
//两个方法创建任务的方式都是new FutureTask
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
FutureTask是Future的一个实现类,也是使用比较多的一个实现类,它除了实现Future接口中定义的get和done方法之外,还额外增加了finish方法。
我们通过一个例子熟悉下submit方法的用法:
public static void main(String[] args) {
ExecutorService execute = Executors.newFixedThreadPool(10);
Future<String> future1 = execute.submit(()->{
TimeUnit.SECONDS.sleep(1);
return "Hello JavaCodeGirl";
});
System.out.println("future1任务执行结果为:" + future1.get());
Future<String> future2 = execute.submit(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("提交一个Runnable类型的任务,不接收结果");
}, null);
System.out.println("future2任务执行结果为:" + future2.get());
AtomicInteger result = new AtomicInteger();
Future<AtomicInteger> future3 = execute.submit(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
result.set(100);
}, result);
System.out.println("future3任务执行结果为:" + future3.get());
execute.shutdown();
}
通过测试代码我们可以发现Future它无法被动的去接收任务,它必须是我们程序员通过get方法去获取计算的结果,但是我们又很难知道任务在哪个时间节点能执行结束,而且任务抛出的异常信息也只能在get方法获取结果时才能知道。
上述代码我们使用了3个Future,他们之间是相互独立的,我们并不能获取到future1的结果将其应用到future2中。
所以开发中我们不常直接使用Future,而是使用JDK1.8引入的新Future:CompletableFuture。
CompletableFuture介绍
CompletableFuture类实现了Future接口,所以它也是一个Future,在开发中我们也可以把它当作Future来使用。但是它还实现了另外一个接口CompletionStage,该接口是同步或者异步任务的某一个阶段。
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
//无参构造函数
public CompletableFuture() {
}
}
我们通过一个例子熟悉下CompletableFuture的用法:
public static void main(String[] args) {
CompletableFuture<String> completableFuture = new CompletableFuture<>();
Executors.newCachedThreadPool().submit(()->{
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
completableFuture.complete("Hello JavaCodeGirl");
});
try {
System.out.println("非阻塞获取结果为:" + completableFuture.getNow(null));
System.out.println("阻塞获取结果为:" + completableFuture.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
CompletableFuture除了具备Future的特性之外,我们还可以利用它提供的静态方法supplyAsync和runAsync来执行异步任务。
supplyAsync类似Callable接口,可返回指定类型的结果。
runAsync的任务类型为Runnable,只关注任务本身的运行,不返回结果。
//supplyAsync执行任务
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->100);
System.out.println(future.get());
//runAsync执行任务
CompletableFuture.runAsync(()->{
System.out.println("Hello Java");
});
CompletableFuture允许将一个任务的执行结果继续传递到下一个任务,可以形成一个任务链。
thenApply/thenApplyAsync:以同步/异步的方式执行上一个异步任务的结果。
thenAccept/thenAcceptAsync:以同步/异步的方式消费上一个异步任务的结果。
thenRun/thenRunAsync:以同步/异步的方式执行Runnable任务。
掌握CompletableFuture的用法,还需要了解JDK1.8引入的Stream流和Lambda表达式,今天先对它有个初印象,下个系列勾勾就和大家一起学习Java8流。
CompletableFuture.supplyAsync(()->{
return "Hello Java";
}).thenApply(e->{
return e + "Code Girl";
}).thenAccept(m ->{
System.out.println(m.length());
}).thenRun(()->{
System.out.println("不想接收任务,只想做自己的事情");
});
CompletableFuture还提供将多个Future合并为一个的thenCompose和thenCombine方法,大家可以自己了解一下喔。
结束语
并发编程系列的学习文章在今天就结束了,当然还有很多勾勾没有写出来的,比如原子类、ForkJoin、并发队列等,在后面的文章中如果涉及到我们再学习。勾勾写学习笔记也持续了一个月了,感谢关注陪伴勾勾的每一位小伙伴。
学完每个系列的知识,我们都应该检验下自己的掌握程度,上学的时候都是通过考试来校验,我们就通过面试题和实战来检验了。
接下来勾勾会推出并发编程面试题分析文章,如果你有面试经历,可以分享给勾勾!!!
勾勾的面试题目有些是勾勾作为面试官面试别人的题目,有的是勾勾作为面试者被别人虐的题目,也有的是勾勾后者脸皮从其他人那里要来的,总之都是100%的真题啦!