• Java线程和多线程(十三)——Callable,Future,FutureTask


    在Java多线程之中,CallableFuture的使用时非常广泛的。在之前的文章中,我们了解了关于Java线程池基础的一些内容,知道如何提交Runnable的任务。但是,Runnable的任务是无法有返回值,也不能抛出异常的。而有些时候,我们希望一个线程能够有一些返回值。在Java 5中,引入了java.util.concurrent.Callable接口,这个接口很类似于Runnable接口,但是可以返回一个对象,或者抛出异常。

    Java Callable

    Java的Callable接口使用了泛型来定义返回的对象的类型。Executors类提供了一些很实用的方法来在线程池中执行Callable的任务。因为Callable的任务通过并行的方式来运行,所以我们需要等待返回的对象。

    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;
    }

    Java Future

    Java的Callable对象返回的就是java.util.concurrent.Future对象。通过使用Java Future对象,我们可以知道Callable任务的执行状态,并且获得返回的对象。Future接口提供get()方法来让开发者可以等待Callable任务的执行,然后获得对应的结果。

    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;
    }

    Java Future提供了一个cancel()方法来取消关联的Callable任务的执行。其中的get()方法是包含一个重载的方法的,我们可以指定等待的时间,而不需要无限期的等待Callable任务的执行。这个方法可以有效的防止一个线程的无限期的阻塞。
    Future也提供一个isDone()和一个isCancelled()方法来找到其关联的Callable任务的执行状态。

    下面是使用Callable的例子,是在一秒之后返回执行任务的名字。我们通过使用Executor框架来并行执行100个任务,然后用Future来获得任务的执行结果。

    package com.sapphire.threads;
    
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class MyCallable implements Callable<String> {
    
        @Override
        public String call() throws Exception {
            Thread.sleep(1000);
            //return the thread name executing this callable task
            return Thread.currentThread().getName();
        }
    
        public static void main(String args[]){
            //Get ExecutorService from Executors utility class, thread pool size is 10
            ExecutorService executor = Executors.newFixedThreadPool(10);
            //create a list to hold the Future object associated with Callable
            List<Future<String>> list = new ArrayList<Future<String>>();
            //Create MyCallable instance
            Callable<String> callable = new MyCallable();
            for(int i=0; i< 100; i++){
                //submit Callable tasks to be executed by thread pool
                Future<String> future = executor.submit(callable);
                //add Future to the list, we can get return value using Future
                list.add(future);
            }
            for(Future<String> fut : list){
                try {
                    //print the return value of Future, notice the output delay in console
                    // because Future.get() waits for task to get completed
                    System.out.println(new Date()+ "::"+fut.get());
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            }
            //shut down the executor service now
            executor.shutdown();
        }
    }

    当我们运行上面程序的时候,我们的输出会有延迟,因为Future的get()方法会一直等待Callable的任务执行完毕。同时需要注意的是,线程池中,我们仅仅会有10个线程来处理之前定义的Callable任务。

    下面是上面程序的输出结果:

    Mon Dec 31 20:40:15 PST 2012::pool-1-thread-1
    Mon Dec 31 20:40:16 PST 2012::pool-1-thread-2
    Mon Dec 31 20:40:16 PST 2012::pool-1-thread-3
    Mon Dec 31 20:40:16 PST 2012::pool-1-thread-4
    Mon Dec 31 20:40:16 PST 2012::pool-1-thread-5
    Mon Dec 31 20:40:16 PST 2012::pool-1-thread-6
    Mon Dec 31 20:40:16 PST 2012::pool-1-thread-7
    Mon Dec 31 20:40:16 PST 2012::pool-1-thread-8
    Mon Dec 31 20:40:16 PST 2012::pool-1-thread-9
    Mon Dec 31 20:40:16 PST 2012::pool-1-thread-10
    Mon Dec 31 20:40:16 PST 2012::pool-1-thread-2
    ...

    当我们想要覆盖掉Future接口的一些行为的时候,举例来说,假设我们需要覆盖其中的get()方法来做一些超时处理而不进行持续等待等操作的时候。Java中的FutureTask类在这种时候就会非常有用了,它是Future接口的实现类。

    FutureTask

    在上面,我们了解到使用Callable以及Future接口来处理多线程的一些便利之处。

    FutureTaskFuture接口的一个基础实现,并且提供了异步处理的功能,FutureTask包含了一些方法来启动或者取消任务,也包含一些方法来返回Future的状态,来确认Future是完成了还是去掉了。我们需要一个Callable对象来创建一个FutureTask然后,我们可以通过ThreadPoolExecutor来异步处理这些任务。

    下面是FutureTask的代码举例,因为FutureTask是需要Callable的,所以我们来创建一个Callable的实现:

    package com.sapphire.threads;
    
    import java.util.concurrent.Callable;
    
    public class MyCallable implements Callable<String> {
    
        private long waitTime;
    
        public MyCallable(int timeInMillis){
            this.waitTime=timeInMillis;
        }
        @Override
        public String call() throws Exception {
            Thread.sleep(waitTime);
            //return the thread name executing this callable task
            return Thread.currentThread().getName();
        }
    
    }

    下面是一个FutureTask方法的例子,下面展示的是关于使用FutureTask方法的一些举例:

    package com.sapphire.threads;
    
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.FutureTask;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.TimeoutException;
    
    public class FutureTaskExample {
    
        public static void main(String[] args) {
            MyCallable callable1 = new MyCallable(1000);
            MyCallable callable2 = new MyCallable(2000);
    
            FutureTask<String> futureTask1 = new FutureTask<String>(callable1);
            FutureTask<String> futureTask2 = new FutureTask<String>(callable2);
    
            ExecutorService executor = Executors.newFixedThreadPool(2);
            executor.execute(futureTask1);
            executor.execute(futureTask2);
    
            while (true) {
                try {
                    if(futureTask1.isDone() && futureTask2.isDone()){
                        System.out.println("Done");
                        //shut down executor service
                        executor.shutdown();
                        return;
                    }
    
                    if(!futureTask1.isDone()){
                        //wait indefinitely for future task to complete
                        System.out.println(
                            "FutureTask1 output="+futureTask1.get());
                    }
    
                    System.out.println("Waiting for FutureTask2 to complete");
                    String s = futureTask2.get(200L, TimeUnit.MILLISECONDS);
                    if(s !=null){
                        System.out.println("FutureTask2 output="+s);
                    }
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }catch(TimeoutException e){
                    //do nothing
                }
            }
        }
    }

    当我们运行上面的程序,你会发现,有一段时间是不会输出任何东西到控制台的,因为FutureTaskget()方法会等待任务的完成,然后才会返回输出的对象。在FutureTask中也有一个重载的方法会等待指定的时间。需要注意的是,当调用isDone()方法来确定程序一旦结束,任务也会完成。

    输出如下:

    FutureTask1 output=pool-1-thread-1
    Waiting for FutureTask2 to complete
    Waiting for FutureTask2 to complete
    Waiting for FutureTask2 to complete
    Waiting for FutureTask2 to complete
    Waiting for FutureTask2 to complete
    FutureTask2 output=pool-1-thread-2
    Done

    从上面的例子来说是没有使用到FutureTask的便利之处的,但是当我们想要覆盖掉Future接口方法的实现,而不像实现Future接口的每一个方法的时候,我们就可以考虑使用FutureTask

  • 相关阅读:
    log4j.properties 配置示例
    spark去重计数操作(代码示例)
    mysql数据库
    Mysql之sql语句操作
    mysql修改root密码的多种方法
    mysql的主从复制过程
    mysql命令用法复习笔记
    Linux下如何查看系统启动时间和运行时间安装时间
    一键系统优化15项脚本
    MongoDB
  • 原文地址:https://www.cnblogs.com/qitian1/p/6461513.html
Copyright © 2020-2023  润新知