zoukankan      html  css  js  c++  java
  • Java多线程编程:Callable、Future和FutureTask浅析

    通过前面几篇的学习,我们知道创建线程的方式有两种,一种是实现Runnable接口,另一种是继承Thread,但是这两种方式都有个缺点,那就是在任务执行完成之后无法获取返回结果,那如果我们想要获取返回结果该如何实现呢?还记上一篇Executor框架结构中提到的Callable接口和Future接口吗?,是的,从Java SE 5.0开始引入了Callable和Future,通过它们构建的线程,在任务执行完成后就可以获取执行结果,今天我们就来聊聊线程创建的第三种方式,那就是实现Callable接口。

    1.Callable<V>接口
    我们先回顾一下java.lang.Runnable接口,就声明了run(),其返回值为void,当然就无法获取结果了。
    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public interface Runnable {  
    2.     public abstract void run();  
    3. }  

    而Callable的接口定义如下

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public interface Callable<V> {   
    2.       V   call()   throws Exception;   
    3. }   

    该接口声明了一个名称为call()的方法,同时这个方法可以有返回值V,也可以抛出异常。嗯,对该接口我们先了解这么多就行,下面我们来说明如何使用,前篇文章我们说过,无论是Runnable接口的实现类还是Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行,ThreadPoolExecutor或ScheduledThreadPoolExecutor都实现了ExcutorService接口,而因此Callable需要和Executor框架中的ExcutorService结合使用,我们先看看ExecutorService提供的方法:

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. <T> Future<T> submit(Callable<T> task);  
    2. <T> Future<T> submit(Runnable task, T result);  
    3. Future<?> submit(Runnable task);  
    第一个方法:submit提交一个实现Callable接口的任务,并且返回封装了异步计算结果的Future。
    第二个方法:submit提交一个实现Runnable接口的任务,并且指定了在调用Future的get方法时返回的result对象。
    第三个方法:submit提交一个实现Runnable接口的任务,并且返回封装了异步计算结果的Future。
    因此我们只要创建好我们的线程对象(实现Callable接口或者Runnable接口),然后通过上面3个方法提交给线程池去执行即可。还有点要注意的是,除了我们自己实现Callable对象外,我们还可以使用工厂类Executors来把一个Runnable对象包装成Callable对象。Executors工厂类提供的方法如下:
    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public static Callable<Object> callable(Runnable task)  
    2. public static <T> Callable<T> callable(Runnable task, T result)  
    2.Future<V>接口
    Future<V>接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行的结果进行获取(get()),取消(cancel()),判断是否完成等操作。我们看看Future接口的源码:
    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public interface Future<V> {  
    2.     boolean cancel(boolean mayInterruptIfRunning);  
    3.     boolean isCancelled();  
    4.     boolean isDone();  
    5.     V get() throws InterruptedException, ExecutionException;  
    6.     V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;  
    7. }  
    方法解析:
    V get() :获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。
    V get(Long timeout , TimeUnit unit) :获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将返回null。
    boolean isDone() :如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。
    boolean isCanceller() :如果任务完成前被取消,则返回true。
    boolean cancel(boolean mayInterruptRunning) :如果任务还没开始,执行cancel(...)方法将返回false;如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;当任务已经完成,执行cancel(...)方法将返回false。mayInterruptRunning参数表示是否中断执行中的线程。
    通过方法分析我们也知道实际上Future提供了3种功能:(1)能够中断执行中的任务(2)判断任务是否执行完成(3)获取任务执行完成后额结果。
    但是我们必须明白Future只是一个接口,我们无法直接创建对象,因此就需要其实现类FutureTask登场啦。
    3.FutureTask类
    我们先来看看FutureTask的实现
    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public class FutureTask<V> implements RunnableFuture<V> {  
    FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现:
    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public interface RunnableFuture<V> extends Runnable, Future<V> {  
    2.     void run();  
    3. }  
    分析:FutureTask除了实现了Future接口外还实现了Runnable接口,因此FutureTask也可以直接提交给Executor执行。 当然也可以调用线程直接执行(FutureTask.run())。接下来我们根据FutureTask.run()的执行时机来分析其所处的3种状态:
    (1)未启动,FutureTask.run()方法还没有被执行之前,FutureTask处于未启动状态,当创建一个FutureTask,而且没有执行FutureTask.run()方法前,这个FutureTask也处于未启动状态。
    (2)已启动,FutureTask.run()被执行的过程中,FutureTask处于已启动状态。
    (3)已完成,FutureTask.run()方法执行完正常结束,或者被取消或者抛出异常而结束,FutureTask都处于完成状态。

    下面我们再来看看FutureTask的方法执行示意图(方法和Future接口基本是一样的,这里就不过多描述了)
    分析:
    (1)当FutureTask处于未启动或已启动状态时,如果此时我们执行FutureTask.get()方法将导致调用线程阻塞;当FutureTask处于已完成状态时,执行FutureTask.get()方法将导致调用线程立即返回结果或者抛出异常。
    (2)当FutureTask处于未启动状态时,执行FutureTask.cancel()方法将导致此任务永远不会执行。
    当FutureTask处于已启动状态时,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果任务取消成功,cancel(...)返回true;但如果执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时cancel(...)返回false。
    当任务已经完成,执行cancel(...)方法将返回false。
    最后我们给出FutureTask的两种构造函数:
    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public FutureTask(Callable<V> callable) {  
    2. }  
    3. public FutureTask(Runnable runnable, V result) {  
    4. }  
    3.Callable<V>/Future<V>/FutureTask的使用
    通过上面的介绍,我们对Callable,Future,FutureTask都有了比较清晰的了解了,那么它们到底有什么用呢?我们前面说过通过这样的方式去创建线程的话,最大的好处就是能够返回结果,加入有这样的场景,我们现在需要计算一个数据,而这个数据的计算比较耗时,而我们后面的程序也要用到这个数据结果,那么这个时Callable岂不是最好的选择?我们可以开设一个线程去执行计算,而主线程继续做其他事,而后面需要使用到这个数据时,我们再使用Future获取不就可以了吗?下面我们就来编写一个这样的实例
    3.1 使用Callable+Future获取执行结果
    Callable实现类如下:
    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. package com.zejian.Executor;  
    2. import java.util.concurrent.Callable;  
    3. /** 
    4.  * @author zejian 
    5.  * @time 2016年3月15日 下午2:02:42 
    6.  * @decrition Callable接口实例 
    7.  */  
    8. public class CallableDemo implements Callable<Integer> {  
    9.       
    10.     private int sum;  
    11.     @Override  
    12.     public Integer call() throws Exception {  
    13.         System.out.println("Callable子线程开始计算啦!");  
    14.         Thread.sleep(2000);  
    15.           
    16.         for(int i=0 ;i<5000;i++){  
    17.             sum=sum+i;  
    18.         }  
    19.         System.out.println("Callable子线程计算结束!");  
    20.         return sum;  
    21.     }  
    22. }  

    Callable执行测试类如下:

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. package com.zejian.Executor;  
    2. import java.util.concurrent.ExecutorService;  
    3. import java.util.concurrent.Executors;  
    4. import java.util.concurrent.Future;  
    5. /** 
    6.  * @author zejian 
    7.  * @time 2016年3月15日 下午2:05:43 
    8.  * @decrition callable执行测试类 
    9.  */  
    10. public class CallableTest {  
    11.       
    12.     public static void main(String[] args) {  
    13.         //创建线程池  
    14.         ExecutorService es = Executors.newSingleThreadExecutor();  
    15.         //创建Callable对象任务  
    16.         CallableDemo calTask=new CallableDemo();  
    17.         //提交任务并获取执行结果  
    18.         Future<Integer> future =es.submit(calTask);  
    19.         //关闭线程池  
    20.         es.shutdown();  
    21.         try {  
    22.             Thread.sleep(2000);  
    23.         System.out.println("主线程在执行其他任务");  
    24.           
    25.         if(future.get()!=null){  
    26.             //输出获取到的结果  
    27.             System.out.println("future.get()-->"+future.get());  
    28.         }else{  
    29.             //输出获取到的结果  
    30.             System.out.println("future.get()未获取到结果");  
    31.         }  
    32.           
    33.         } catch (Exception e) {  
    34.             e.printStackTrace();  
    35.         }  
    36.         System.out.println("主线程在执行完成");  
    37.     }  
    38. }  
    执行结果:
    Callable子线程开始计算啦!
    主线程在执行其他任务
    Callable子线程计算结束!
    future.get()-->12497500
    主线程在执行完成
    3.2 使用Callable+FutureTask获取执行结果
    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. package com.zejian.Executor;  
    2. import java.util.concurrent.ExecutorService;  
    3. import java.util.concurrent.Executors;  
    4. import java.util.concurrent.Future;  
    5. import java.util.concurrent.FutureTask;  
    6. /** 
    7.  * @author zejian 
    8.  * @time 2016年3月15日 下午2:05:43 
    9.  * @decrition callable执行测试类 
    10.  */  
    11. public class CallableTest {  
    12.       
    13.     public static void main(String[] args) {  
    14. //      //创建线程池  
    15. //      ExecutorService es = Executors.newSingleThreadExecutor();  
    16. //      //创建Callable对象任务  
    17. //      CallableDemo calTask=new CallableDemo();  
    18. //      //提交任务并获取执行结果  
    19. //      Future<Integer> future =es.submit(calTask);  
    20. //      //关闭线程池  
    21. //      es.shutdown();  
    22.           
    23.         //创建线程池  
    24.         ExecutorService es = Executors.newSingleThreadExecutor();  
    25.         //创建Callable对象任务  
    26.         CallableDemo calTask=new CallableDemo();  
    27.         //创建FutureTask  
    28.         FutureTask<Integer> futureTask=new FutureTask<>(calTask);  
    29.         //执行任务  
    30.         es.submit(futureTask);  
    31.         //关闭线程池  
    32.         es.shutdown();  
    33.         try {  
    34.             Thread.sleep(2000);  
    35.         System.out.println("主线程在执行其他任务");  
    36.           
    37.         if(futureTask.get()!=null){  
    38.             //输出获取到的结果  
    39.             System.out.println("futureTask.get()-->"+futureTask.get());  
    40.         }else{  
    41.             //输出获取到的结果  
    42.             System.out.println("futureTask.get()未获取到结果");  
    43.         }  
    44.           
    45.         } catch (Exception e) {  
    46.             e.printStackTrace();  
    47.         }  
    48.         System.out.println("主线程在执行完成");  
    49.     }  
    50. }  
    执行结果:
    Callable子线程开始计算啦!
    主线程在执行其他任务
    Callable子线程计算结束!
    futureTask.get()-->12497500
    主线程在执行完成
  • 相关阅读:
    LintCode Python 简单级题目 488.快乐数
    LintCode Python 简单级题目 100.删除排序数组中的重复数字 101.删除排序数组中的重复数字II
    LintCode Python 简单级题目 373.奇偶分割数组
    LintCode Python 简单级题目 39.恢复旋转排序数组
    LintCode Python 简单级题目 35.翻转链表
    LintCode Python 简单级题目 451.两两交换链表中的节点
    LintCode Python 简单级题目 174.删除链表中倒数第n个节点
    aws查看官方centos镜像imageid
    linux shell脚本查找重复行/查找非重复行/去除重复行/重复行统计
    php配置优化-生产环境应用版
  • 原文地址:https://www.cnblogs.com/lcngu/p/6863529.html
Copyright © 2011-2022 走看看