zoukankan      html  css  js  c++  java
  • Runnable、Callable、Future和FutureTask之一:基本用法

      Java从发布的第一个版本开始就可以很方便地编写多线程的应用程序,并在设计中引入异步处理。Thread类、Runnable接口和Java内存管理模型使得多线程编程简单直接。但正如之前提到过的,Thread类和Runnable接口都不允许声明检查型异常,也不能定义返回值。没有返回值这点稍微有点麻烦。

     

     不能声明抛出检查型异常则更麻烦一些。public void run()方法契约意味着你必须捕获并处理检查型异常。即使你小心地保存了异常信息(译者注:在捕获异常时)以便稍后检查,但也不能保证这个类(译者注:Runnable对象)的所有使用者都读取异常信息。你也可以修改Runnable实现的getter,让它们都能抛出任务执行中的异常。但这种方法除了繁琐也不是十分安全可靠,你不能强迫使用者调用这些方法,程序员很可能会调用join()方法等待线程结束然后就不管了。

    但是现在不用担心了,以上的问题终于在1.5中解决了。Callable接口和Future接口的引入以及他们对线程池的支持优雅地解决了这两个问题。

    今天我们就来讨论一下Callable、Future和FutureTask三个类的使用方法。以下是本文的目录大纲:

    一.Callable与Runnable

    二.Future

    三.FutureTask

    四.使用示例

    一、Callable与Runnable

    先说一下java.lang.Runnable吧,它是一个接口,在它里面只声明了一个run()方法:

    public interface Runnable {
        public abstract void run();
    }
    由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。

    Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其他线程执行的任务。但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值。

    Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做call():

    public interface Callable<V> {
        /**
         * Computes a result, or throws an exception if unable to do so.
         *
         * @return computed result
         * @throws Exception if unable to compute a result
         */
        V call() throws Exception;
    }

    可以看到,这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。

    那么怎么使用Callable呢?一般情况下是配合ExecutorService来使用的,在ExecutorService接口中声明了若干个submit方法的重载版本:

    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);

    第一个submit方法里面的参数类型就是Callable。

    暂时只需要知道Callable一般是和ExecutorService配合来使用的,具体的使用方法讲在后面讲述。

    一般情况下我们使用第一个submit方法和第三个submit方法,第二个submit方法很少使用。

    总结:Callable和Runnable的区别如下:

    1. Callable定义的方法是call,而Runnable定义的方法是run。
    2. Callable的call方法可以有返回值,而Runnable的run方法不能有返回值。
    3. Callable的call方法可抛出异常,而Runnable的run方法不能抛出异常。
     
    二、Future

    Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

    或者说,Future 提供了另一种在操作完成时通知应用程序的方式。这个对象可以看作是一个异步操作的结果的占位符;它将在未来的某个时刻完成,并提供对其结果的访问。

    Future类位于java.util.concurrent包下,它是一个接口:

    public interface Future<V> {
        boolean cancel(boolean mayInterruptIfRunning);
        boolean isCancelled();
        boolean isDone();
        V get() throws InterruptedException, ExecutionException;
        V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
    }

    在Future接口中声明了5个方法,下面依次解释每个方法的作用:

    • cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
    • isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
    • isDone方法表示任务是否已经完成,若任务完成,则返回true;
    • get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
    • get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

    也就是说Future提供了三种功能:

      1)判断任务是否完成;

      2)能够中断任务;

      3)能够获取任务执行结果。

    因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。

     
    三、FutureTask

    我们先来看一下FutureTask的实现:

    public class FutureTask<V> implements RunnableFuture<V>

    FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现:

    public interface RunnableFuture<V> extends Runnable, Future<V> {
        void run();
    }

    可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

      FutureTask提供了2个构造器:

    public FutureTask(Callable<V> callable) {
    }
    public FutureTask(Runnable runnable, V result) {
    }

    事实上,FutureTask是Future接口的一个唯一实现类。

    四、使用示例

    1.使用Callable+Future+ExecutorService 获取执行结果,Executor使我们无需显示的去管理线程的生命周期,是JDK 5之后启动任务的首选方式

    public class Test {
        public static void main(String[] args) {
            ExecutorService executor = Executors.newCachedThreadPool();
            Task task = new Task();
            Future<Integer> result = executor.submit(task);
            executor.shutdown();
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
    
            System.out.println("主线程在执行任务");
    
            try {
                System.out.println("task运行结果" + result.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
    
            System.out.println("所有任务执行完毕");
        }
    }
    
    class Task implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("子线程在进行计算");
            Thread.sleep(3000);
            int sum = 0;
            for (int i = 0; i < 100; i++)
                sum += i;
            return sum;
        }
    }

    结果:

    子线程在进行计算
    主线程在执行任务
    task运行结果4950
    所有任务执行完毕

    2.使用Callable+FutureTask+Thread获取执行结果

    public class Test {
        public static void main(String[] args) {
            // 第一种方式
            /**
            ExecutorService executor = Executors.newCachedThreadPool();
            Task task = new Task();
            FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
            executor.submit(futureTask);
            executor.shutdown();
            */
            // 第二种方式,注意这种方式和第一种方式效果是类似的,只不过一个使用的是ExecutorService,一个使用的是Thread
    
            Task task = new Task();
            FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
            Thread thread = new Thread(futureTask);
            thread.start();
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
    
            System.out.println("主线程在执行任务");
    
            try {
                System.out.println("task运行结果" + futureTask.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
    
            System.out.println("所有任务执行完毕");
        }
    }
    
    class Task implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("子线程在进行计算");
            Thread.sleep(3000);
            int sum = 0;
            for (int i = 0; i < 100; i++)
                sum += i;
            return sum;
        }
    }

    结果:

    子线程在进行计算
    主线程在执行任务
    task运行结果4950
    所有任务执行完毕

    3、 执行多个带返回值的任务,并取得多个返回值,代码如下:

    public class CallableAndFuture2 {
        public static void main(String[] args) {
            ExecutorService threadPool = Executors.newCachedThreadPool();  
            CompletionService<Integer> cs = new ExecutorCompletionService<Integer>(threadPool);  
            for(int i = 1; i < 5; i++) {  
                final int taskID = i;  
                cs.submit(new Callable<Integer>() {  
                    public Integer call() throws Exception {  
                        return taskID;  
                    }  
                });  
            }  
            // 可能做一些事情  
            for(int i = 1; i < 5; i++) {  
                try {  
                    System.out.println("主线程获取第"+i+"个子线程的结果:" + cs.take().get());  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                } catch (ExecutionException e) {  
                    e.printStackTrace();  
                }  
            }  
            
        }
    }

    结果:

    主线程获取第1个子线程的结果:1
    主线程获取第2个子线程的结果:2
    主线程获取第3个子线程的结果:4
    主线程获取第4个子线程的结果:3

    其实也可以不使用CompletionService,可以先创建一个装Future类型的集合,用Executor提交的任务返回值添加到集合中,最后遍历集合取出数据,代码略。     

    转自:http://www.cnblogs.com/dolphin0520/p/3949310.html

  • 相关阅读:
    基本技能训练之线程
    关于UEditor的使用配置(图片上传配置)
    PAT 乙级练习题1002. 写出这个数 (20)
    codeforces 682C Alyona and the Tree DFS
    codeforces 681D Gifts by the List dfs+构造
    codeforces 678E Another Sith Tournament 概率dp
    codeforces 680E Bear and Square Grid 巧妙暴力
    codeforces 678D Iterated Linear Function 矩阵快速幂
    codeforces 679A Bear and Prime 100 交互
    XTUOJ 1248 TC or CF 搜索
  • 原文地址:https://www.cnblogs.com/duanxz/p/4489146.html
Copyright © 2011-2022 走看看