首页 / 资讯中心 / 文章详情

多线程高并发编程(7) -- Future源码分析

多线程高并发编程(7) -- Future源码分析 ★ FEATURED ARTICLE
源码仓库Gitee · GitHub一.概念Future可获取计算的结果。 它有提供方法来检查计算是否完成等待其完成并检索计算结果。 结果只能在计算完成后使用方法get进行检索如有必要阻塞直到准备就绪。 取消由cancel方法执行。 提供其他方法来确定任务是否正常完成或被取消。 计算完成后不能取消计算。 如果您想使用Future 以便不可撤销但不提供可用的结果则可以声明Future?表格的类型并返回null作为基础任务的结果。public interface FutureV { //尝试取消执行此任务。如果任务已经完成已经被取消或由于某些其他原因而无法取消则此尝试将失败。 //如果成功并且在调用 cancel 时此任务尚未开始则该任务永远无法运行。 //如果任务已经开始则 mayInterruptIfRunning 参数确定是否应中断执行该任务的线程以尝试停止该任务。 //mayInterruptIfRunning true 表示中断执行中的线程false 表示让线程正常完成 boolean cancel(boolean mayInterruptIfRunning); //如果此任务在正常完成之前被取消则返回true。 boolean isCancelled(); //如果此任务完成则返回true。完成可能是由于正常终止异常或取消引起的在所有这些情况下此方法都将返回true。 boolean isDone(); //必要时等待计算完成然后检索其结果 V get() throws InterruptedException, ExecutionException; //必要时最多等待给定时间以完成计算然后检索其结果如果有。 V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }Future是一个接口提供了方法来检测当前的任务是否已经结束还可以等待任务结束并且拿到一个结果通过调用Future的get()方法可以当任务结束后返回一个结果值如果工作没有结束则会阻塞当前线程直到任务执行完毕可以通过调用cancel()方法来停止一个任务如果任务已经停止则cancel()方法会返回true如果任务已经完成或者已经停止了或者这个任务无法停止则cancel()会返回一个false。当一个任务被成功停止后他无法再次执行。isDone()和isCancel()方法可以判断当前工作是否完成和是否取消。类图结构ScheduledFuture这个接口表示一个延时的行为可以被取消。通常一个安排好的future是定时任务SchedualedExecutorService的结果RunnableFuture 这个接口同时继承Future接口和Runnable接口在成功执行run()方法后可以通过Future访问执行结果ForkJoinTask基于任务的抽象类可以通过ForkJoinPool来执行。一个ForkJoinTask是类似于线程实体但是相对于线程实体是轻量级的。大量的任务和子任务会被ForkJoinPool池中的真实线程挂起来以某些使用限制为代价CompletableFuture一个Future类是显示的完成而且能被用作一个完成等级通过它的完成触发支持的依赖函数和行为。当两个或多个线程要执行完成或取消操作时只有一个能够成功RunnableScheduledFuture执行延迟和周期性任务在成功执行run()方法后可以通过Future访问执行结果FutureTask可取消的异步计算该类提供了一个Future的基本实现 具有启动和取消计算的方法查询计算是否完整并检索计算结果。结果只能在计算完成后才能检索; 如果计算尚未完成则get方法将阻止。一旦计算完成则无法重新启动或取消计算除非使用runAndReset()调用计算 FutureTask可用于包装Callable或Runnable对象。 因为FutureTask实现Runnable 一个FutureTask可以提交到一个Executor执行RecursiveTask递归结果ForkJoinTaskRecursiveAction递归结果ForkJoinTask二.用法一个场景我们要学习做饭那么我们需要准备厨具和食材厨具通过电子商务网购食材去菜市场挑选。那么可以使用多线程来并发进行即我们可以先网购下单在等待快递员送货过来的这段时间去菜市场买食材节省时间提高效率。直接开启线程使用类继承Thread重写方法实现网购join阻塞直到厨具到达才开始做饭。public class FutureTest { public static void main(String[] args) throws InterruptedException { long startTime System.currentTimeMillis(); OnlineShopping shopping new OnlineShopping(); shopping.start(); Thread.sleep(2000);//等待送货执行完 System.out.println(第二步食材到位); shopping.join();//阻塞订单直到快递送到得到厨具 System.out.println(第三步开始厨艺); System.out.println(总共用时 (System.currentTimeMillis() - startTime) ms); } static class OnlineShopping extends Thread { Override public void run() { System.out.println(第一步下单); System.out.println(第一步等待送货); try { Thread.sleep(5000);//送货中 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(第一步快递送到); } } } //结果 第一步下单 第一步等待送货 第二步食材到位 第一步快递送到 第三步开始厨艺 总共用时5003ms使用Future模式来完成上述操作通过Callable返回结果来获取厨具可以通过FutureTask灵活地操作订单由此可见比继承Thread完成的订单Future模式更具有灵活性public class FutureTest { public static void main(String[] args) throws Exception { long startTime System.currentTimeMillis(); CallableString shopping () -{ System.out.println(第一步下单); System.out.println(第一步等待送货); Thread.sleep(5000);//快递员送货中 System.out.println(第一步快递送到); return 厨具到达; }; FutureTaskString task new FutureTask(shopping); new Thread(task).start(); Thread.sleep(2000);//保证下单操作执行到“等待送货”中 System.out.println(第二步食材到位); if (!task.isDone()) { // 联系快递员询问是否到货 System.out.println(第三步厨具还没到心情好就等着心情不好就调用cancel方法取消订单); } String chuju task.get();//得到厨具 System.out.println(第三步开始厨艺); System.out.println(总共用时 (System.currentTimeMillis() - startTime) ms); } } //结果 第一步下单 第一步等待送货 第二步食材到位 第三步厨具还没到心情好就等着心情不好就调用cancel方法取消订单 第一步快递送到 第三步开始厨艺 总共用时5048ms三.分析使用Future模式的三部曲创建Callable重写call方法把网购逻辑封装到call中返回定义结果“厨具”public interface CallableV { V call() throws Exception; }创建FutureTask把Callable实例放入FutureTask的构造方法中public class FutureTaskV implements RunnableFutureV{ public FutureTask(CallableV callable) { if (callable null) throw new NullPointerException(); this.callable callable; this.state NEW; // 确保callable的可见性 } public FutureTask(Runnable runnable, V result) { this.callable Executors.callable(runnable, result); this.state NEW; // 确保callable的可见性 } } public interface RunnableFutureV extends Runnable, FutureV { void run(); }FutureTask的run方法Callable的call是被FutureTask的run方法调用的不是异步运行的public void run() { // 1. 如果 state ! NEW 说明 run 方法已经运行过直接 return // 2. 如果 state NEW CAS 竞争 设置 runner 失败说明已经有别的线程在运行直接 return // NEW 的状态由构造方法初始化runner 是运行该 Callable 的线程 if (state ! NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; try { CallableV c callable;// 这里的callable是从构造方法里面传人的 if (c ! null state NEW) { V result; boolean ran;// 标识符 try { result c.call();//获得结果 ran true; } catch (Throwable ex) {//异常 result null; ran false; setException(ex); } if (ran)//成功没有异常设置返回值 set(result); } } finally { // runner must be non-null until state is settled to // prevent concurrent calls to run() //在状态设置之前runner必须是非空的以防止对run()的并发调用 runner null; // state must be re-read after nulling runner to prevent // leaked interrupts //为防止泄漏中断必须在空runner之后将状态设置为重复读 int s state; // 如果最终状态 INTERRUPTING则处理中断 // cancel 方法会通过参数 mayInterruptIfRunning 来设置 state 的值 if (s INTERRUPTING) handlePossibleCancellationInterrupt(s); } }状态属性stateprivate volatile int state;//状态volatile让状态可见性 private static final int NEW 0;//构造方法创建时的状态 private static final int COMPLETING 1;//这是一个中间态完成时和出现异常时有使用到 private static final int NORMAL 2;//完成运行时的最终状态 private static final int EXCEPTIONAL 3;//异常时的最终状态 private static final int CANCELLED 4;//已取消 private static final int INTERRUPTING 5;//中断中 private static final int INTERRUPTED 6;//已中断 可能的 state 转换 NEW - COMPLETING - NORMAL NEW - COMPLETING - EXCEPTIONAL NEW - CANCELLED NEW - INTERRUPTING - INTERRUPTEDset设置返回值private Object outcome;//通过get方法获得的返回值 //设置返回值状态NEW - COMPLETING - NORMAL protected void set(V v) { // 这里为什么要用 CAS 因为可能会和 cancel 方法产生竞争。 // 如果竞争失败说明取消竞争成功在 cancel 方法承担唤醒的工作所以直接跳过。 if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {//NEW - COMPLETING // 竞争成功 outcome v;//outcome为返回结果 UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // 最终状态为NORMALCOMPLETING - NORMAL finishCompletion(); } }setException运行时异常//状态NEW - COMPLETING - EXCEPTIONAL protected void setException(Throwable t) { // 这里为什么要用 CAS 因为可能会和 cancel 方法产生竞争。 // 如果竞争失败说明取消竞争成功在 cancel 方法承担唤醒的工作所以直接跳过。 if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {//NEW - COMPLETING // 竞争成功 outcome t; // outcome 为一个 Throwable // 把最终状态改为 EXCEPTIONAL UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final stateCOMPLETING - EXCEPTIONAL finishCompletion(); } }finishCompletion//删除当前线程并唤醒所有等待线程调用done()并取消进行中的方法 private void finishCompletion() { // assert state COMPLETING; //从 waiters 末尾开始遍历for 自旋直到 CAS 成功。 for (WaitNode q; (q waiters) ! null;) { // 使用 CAS 把 waiters 设置为 null和 awaitDone 和 removeWatier 方法竞争 if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) { // 自旋唤醒所有线程 for (;;) { Thread t q.thread; if (t ! null) { q.thread null; LockSupport.unpark(t);// 唤醒线程 } WaitNode next q.next; if (next null) break; q.next null; // unlink to help gc q next; } break; } } // 这个一个空方法主要用于继承重写这也是模板模式的体现 done(); callable null; // to reduce footprint } protected void done() { } //例子 //ExecutorCompletionService 的作用就是把线程池的执行结果放到一个已完成队列中方便获取执行结果其内部主要通过一个 FutureTask 的实现类 QueueingFuture 来实现这个功能 private class QueueingFuture extends FutureTaskVoid { QueueingFuture(RunnableFutureV task) { super(task, null); this.task task; } protected void done() { completionQueue.add(task); }//done方法是FutureTask方法的重写。FutureTask在完成时会执行done方法把task放入已完成队列completionQueue。 private final FutureV task; }get获得返回结果public V get() throws InterruptedException, ExecutionException { int s state;//得到状态 if (s COMPLETING)//状态未完成把获取结果的线程放入等待链表然后阻塞直至被中断、完成或出现异常。 s awaitDone(false, 0L); return report(s);//返回结果 } private int awaitDone(boolean timed, long nanos) throws InterruptedException { final long deadline timed ? System.nanoTime() nanos : 0L;//用于计时 WaitNode q null; boolean queued false; for (;;) {//自旋 //如果已经被中断则removeWaiter抛出中断异常 if (Thread.interrupted()) { removeWaiter(q); throw new InterruptedException(); } int s state; if (s COMPLETING) {//task已经结束 if (q ! null) q.thread null; return s; } //第二个线程进来正在运行发现前面有等待节点则让出cpu else if (s COMPLETING) // cannot time out yet Thread.yield(); // 第一次遍历初始化 WaitNode第一个节点进来时进行第一个线程状态是new else if (q null) q new WaitNode(); // 是否已入队没有则把WaitNode接到末尾第一个线程第二次遍历时运行下面代码 else if (!queued) // 和 finishCompletion 和 removeWaiter 竞争 // 1. finishCompletion竞争成功说明state已经 COMPLETING则下次循环就会退出 // 2. removeWaiter竞争成功说明waiters变化了下一次循环再次竞争 queued UNSAFE.compareAndSwapObject(this, waitersOffset, q.next waiters, q); // 如果使用了计时则判断是否超时如果超时则移出WaitNode并立即返回无需等待结果否则阻塞 nanos else if (timed) { nanos deadline - System.nanoTime(); if (nanos 0L) { removeWaiter(q); return state; } LockSupport.parkNanos(this, nanos); } else //阻塞直到被唤醒正常完成 || 异常 || 中断 LockSupport.park(this); } } //根据awaitDone返回状态返回结果或抛出异常 private V report(int s) throws ExecutionException { Object x outcome; if (s NORMAL)//正常 return (V)x; if (s CANCELLED)//取消 throw new CancellationException(); // task 执行过程中出现异常 throw new ExecutionException((Throwable)x); }removeWaiter/** * Tries to unlink a timed-out or interrupted wait node to avoid * accumulating garbage. Internal nodes are simply unspliced * without CAS since it is harmless if they are traversed anyway * by releasers. To avoid effects of unsplicing from already * removed nodes, the list is retraversed in case of an apparent * race. This is slow when there are a lot of nodes, but we dont * expect lists to be long enough to outweigh higher-overhead * schemes. *尝试取消链接超时或中断的等待节点以避免堆积垃圾。内部节点的拼接没有CAS *因为这对释放者无论如何遍历都没有影响。 为了避免已删除节点节点未拼接的影响 *如果出现明显的竞争则重新遍历列表。 当节点很多时会很慢但是我们不 *期望列表足够长以抵消较高的开销计划。 */ private void removeWaiter(WaitNode node) { if (node ! null) { node.thread null; retry: for (;;) { // restart on removeWaiter race //遍历整个链表 for (WaitNode pred null, q waiters, s; q ! null; q s) { s q.next; //把q当作前一个节点遍历下一个节点 if (q.thread ! null) pred q; // q.thread null pred ! null表示当前节点不是第一个节点是一个中间节点 // 这里没有使用 CAS如果出现多个线程同时遍历前一个节点变为null则重新从头遍历 // 为什么没有使用 CAS 因为作者的想法是这个链表不会太长所以我们使用时不应该使这个链表太长 // 操作把下一个节点连接到前一个节点的后面 else if (pred ! null) { pred.next s;//把s连接到pred后面 if (pred.thread null) // check for race continue retry; } // q.thread null pred null表示第一个节点的 thread null // 这里使用 CAS因为可能多个线程在操作 // 操作把下一个节点设置为末尾节点如果竞争失败则重新从头遍历 else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s)) continue retry; } break; } } }isDonepublic boolean isDone() { return state ! NEW; }创建Thread把FutureTask实例放入构造方法中start开启线程参考https://www.jianshu.com/p/414cc2f0002c欢迎任何形式的转载但请务必注明出处。如果觉得文章还行希望推荐关注下谢谢限于本人水平如果文章和代码有表述不当之处还请不吝赐教。
阅读完成 · 觉得有帮助?
咨询建站