在开发应用系统过程中,通过异步并发并不能使执行速度变快,更多的时候是为了通过并发充分利用CPU以及提升系统的吞吐量。
一、Future接口
Future接口在java.util.concurrent包下,它用来处理异步任务的执行结果,在异步任务执行完成之前会阻塞该方法。
方法摘要:
get: 用户获取任务执行结果
cancel: 取消执行的任务
isDone: 判断是否完成
isCancelled: 判断是否主动取消
二、异步实现
下面使用线程池配合Future实现该示例,但是请注意:阻塞请求主线程,高并发时候仍然会造成线程数过多,CPU上下文切换。通过Future可以并发出N个请求,然后等待最慢的一个返回,总响应时间为最慢的一个请求返回的结果用时。
代码示例:
import java.util.concurrent.*; /** * @author lay * @date 2018/5/28. * @time 00:43 */ public class FutureTest { public static void main(String[] args){ // 创建固定线程池 ExecutorService executorService = Executors.newFixedThreadPool(2); long start = System.currentTimeMillis(); // 提交执行两个线程 Future<String> future = executorService.submit(new CallableTask("task one", 1000)); Future<String> future2 = executorService.submit(new CallableTask("task two", 2000)); try { System.out.println(future.get()); System.out.println(future2.get()); } catch (Exception e) { if (future != null) { // 取消执行 future.cancel(true); } if (future2 != null) { // 取消执行 future2.cancel(true); } // 抛出回滚 throw new RuntimeException(e); } System.out.println("总共消耗时间:" + (System.currentTimeMillis() - start)); // 关闭线程池 executorService.shutdown(); } } class CallableTask implements Callable<String> { private String taskName; private long exeTime; public CallableTask(String taskName, long exeTime) { this.taskName = taskName; this.exeTime = exeTime; } @Override public String call() throws Exception { System.out.println(taskName + " is executing"); Thread.sleep(exeTime); return taskName + " is end"; } }
以上代码输出结果:
task one is executing
task two is executing
task one is end
task two is end
总共消耗时间:2011
1)我们通过Executors创建了一个可以容纳两个线程的线程池;
2)提交两个实现了Callable接口的异步类实例到线程池,并执行;
3)通过Future来处理两个线程的执行结果,并打印;
4)如果发生异常则取消执行,并抛出运行时异常;
5)关闭线程池
注意:我们看到总共消耗时间约等于2秒,也就是等同于两个线程耗时最长的那个线程。通过输出结果我们可以了解到,两个线程被执行的时候,主线程阻塞等待线程结果。
参考:《亿级流量网站架构核心技术》—— 张开涛