• java.util.concuttent Callable Future详解


    在传统的多线程实现方式中(继承Thread和实现Runnable)无法直接获取线程执行的返回结果,如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。

    从Java 1.5开始,java.util.concurrent包中提供了 Callable和 Future两个接口,通过它们就可以在任务执行完毕之后得到任务执行结果。

    Callable

    Callable与Runnable的功能大致相似,Callable中有一个call()函数,但是call()函数有返回值,而Runnable的run()函数不能将结果返回给客户程序。

    Future

    Executor就是Runnable和Callable的调度容器,Future就是对于具体的Runnable或者Callable任务的执行结果进行

    取消、查询是否完成、获取结果、设置结果操作。get方法会阻塞,直到任务返回结果。

    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;
    }
    
    • 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提供了三种功能:

    • 判断任务是否完成
    • 能够中断任务
    • 能够获取任务的执行结果

    Future和FutureTask区别

    Future是一个接口, FutureTask类是Future 的一个实现类,并实现了Runnable,因此FutureTask可以传递到线程对象Thread中新建一个线程执行。所以可通过Excutor(线程池) 来执行,也可传递给Thread对象执行。如果在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。 

    FutureTask是为了弥补Thread的不足而设计的,它可以让程序员准确地知道线程什么时候执行完成并获得到线程执行完成后返回的结果(如果有需要)。

    FutureTask是一种可以取消的异步的计算任务。它的计算是通过Callable实现的,它等价于可以携带结果的Runnable,并且有三个状态:等待、运行和完成。完成包括所有计算以任意的方式结束,包括正常结束、取消和异常。

    Executor框架利用FutureTask来完成异步任务,并可以用来进行任何潜在的耗时的计算。一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。

    FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果

    Example1

    package cn.com.example.concurrent.future;
    
    import java.util.concurrent.*;
    
    /**
     * Created by Jack on 2017/1/24.
     */
    public class RunnableFutureTask {
    
        /**
         * ExecutorService
         */
        static ExecutorService mExecutor = Executors.newSingleThreadExecutor();
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            runnableDemo();
            futureDemo();
        }
    
        /**
         * runnable, 无返回值
         */
        static void runnableDemo() {
    
            new Thread(new Runnable() {
    
                @Override
                public void run() {
                    System.out.println("runnable demo : " + fibc(20));
                }
            }).start();
        }
    
        /**
         * 其中Runnable实现的是void run()方法,无返回值;Callable实现的是 V
         * call()方法,并且可以返回执行结果。其中Runnable可以提交给Thread来包装下
         * ,直接启动一个线程来执行,而Callable则一般都是提交给ExecuteService来执行。
         */
        static void futureDemo() {
            try {
                /**
                 * 提交runnable则没有返回值, future没有数据
                 */
                Future<?> result = mExecutor.submit(new Runnable() {
                    @Override
                    public void run() {
                        fibc(20);
                    }
                });
    
                System.out.println("future result from runnable : " + result.get());
    
                /**
                 * 提交Callable, 有返回值, future中能够获取返回值
                 */
                Future<Integer> result2 = mExecutor.submit(new Callable<Integer>() {
                    @Override
                    public Integer call() throws Exception {
                        return fibc(20);
                    }
                });
    
                System.out.println("future result from callable : " + result2.get());
    
                /**
                 * FutureTask则是一个RunnableFuture<V>,即实现了Runnbale又实现了Futrue<V>这两个接口,
                 * 另外它还可以包装Runnable(实际上会转换为Callable)和Callable
                 * <V>,所以一般来讲是一个符合体了,它可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行
                 * ,并且还可以通过v get()返回执行结果,在线程体没有执行完成的时候,主线程一直阻塞等待,执行完则直接返回结果。
                 */
                FutureTask<Integer> futureTask = new FutureTask<Integer>(
                        new Callable<Integer>() {
                            @Override
                            public Integer call() throws Exception {
                                return fibc(20);
                            }
                        });
                // 提交futureTask
                mExecutor.submit(futureTask);
                System.out.println("future result from futureTask : " + futureTask.get());
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 效率底下的斐波那契数列, 耗时的操作
         *
         * @param num
         * @return
         */
        static int fibc(int num) {
            if (num == 0) {
                return 0;
            }
            if (num == 1) {
                return 1;
            }
            return fibc(num - 1) + fibc(num - 2);
        }
    
    }
    

    结果:

    runnable demo : 6765
    future result from runnable : null
    future result from callable : 6765
    future result from futureTask : 6765
    

    Example2:

    package cn.com.example.concurrent.future;
    
    import java.util.Random;
    import java.util.concurrent.*;
    
    /**
     * Created by Jack on 2017/1/24.
     */
    public class RunnableFutureTask {
    
        public static void main(String[] args) {
            // 初始化一个Callable对象和FutureTask对象
            Callable pAccount = new PrivateAccount();
            FutureTask futureTask = new FutureTask(pAccount);
            // 使用futureTask创建一个线程
            Thread pAccountThread = new Thread(futureTask);
            System.out.println("futureTask线程现在开始启动,启动时间为:" + System.nanoTime());
            pAccountThread.start();
            System.out.println("主线程开始执行其他任务");
            // 从其他账户获取总金额
            int totalMoney = new Random().nextInt(100000);
            System.out.println("现在你在其他账户中的总金额为" + totalMoney);
            System.out.println("等待私有账户总金额统计完毕...");
            // 测试后台的计算线程是否完成,如果未完成则等待
            while (!futureTask.isDone()) {
                try {
                    Thread.sleep(500);
                    System.out.println("私有账户计算未完成继续等待...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("futureTask线程计算完毕,此时时间为" + System.nanoTime());
            Integer privateAccountMoney = null;
            try {
                privateAccountMoney = (Integer) futureTask.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            System.out.println("您现在的总金额为:" + totalMoney + privateAccountMoney.intValue());
        }
    }
    
    class PrivateAccount implements Callable {
        Integer totalMoney;
    
        @Override
        public Object call() throws Exception {
            Thread.sleep(5000);
            totalMoney = new Integer(new Random().nextInt(10000));
            System.out.println("您当前有" + totalMoney + "在您的私有账户中");
            return totalMoney;
        }
    
    }
    

    结果:

    futureTask线程现在开始启动,启动时间为:88171383410225
    主线程开始执行其他任务
    现在你在其他账户中的总金额为2838
    等待私有账户总金额统计完毕...
    私有账户计算未完成继续等待...
    私有账户计算未完成继续等待...
    私有账户计算未完成继续等待...
    私有账户计算未完成继续等待...
    私有账户计算未完成继续等待...
    私有账户计算未完成继续等待...
    私有账户计算未完成继续等待...
    私有账户计算未完成继续等待...
    私有账户计算未完成继续等待...
    您当前有9238在您的私有账户中
    私有账户计算未完成继续等待...
    futureTask线程计算完毕,此时时间为88176389195218
    您现在的总金额为:28389238
    
  • 相关阅读:
    Thumbnailator压缩图片
    dubbo序列化的一点注意
    Java编程思想读书笔记之内部类
    Hello World
    sql中where和having的区别
    Linux下服务器搭建
    maven中profile的激活方式
    <![CDATA[ ]]>
    linux下用xampp安装php集成环境,并修改各自端口号
    关于星号(**/*.java)
  • 原文地址:https://www.cnblogs.com/Zombie-Xian/p/6347580.html
Copyright © 2020-2023  润新知