JAVA多线程提高七:Callable与Future的应用

Callable与Runnable

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

public interface Runnable {
     public abstract void run();
}

由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。 
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方法和第三个submit方法,第二个submit方法很少使用。第一个submit方法里面的参数类型就是Callable。

Future

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

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方法用来取消任务:

当你想要取消你已提交给执行者的任务,使用Future接口的cancel()方法。根据cancel()方法参数和任务的状态不同,这个方法的行为将不同:

  • 1、如果这个任务已经完成或之前的已被取消或由于其他原因不能被取消,那么这个方法将会返回false并且这个任务不会被取消。
  • 2、 如果这个任务正在等待执行者获取执行它的线程,那么这个任务将被取消而且不会开始它的执行。如果这个任务已经正在运行,则视方法的参数情况而定。 cancel()方法接收一个Boolean值参数(mayInterruptIfRunning)。如果参数为true并且任务正在运行,那么这个任务将被取消。如果参数为false并且任务正在运行,那么这个任务将不会被取消。

  • isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。

  • isDone方法表示任务是否已经完成,若任务完成,则返回true;

  • get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;

  • get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

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

1)判断任务是否完成;
2)能够中断任务;
3)能够获取任务执行结果。

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

FutureTask

集Runnable、Callable、Future于一身,它首先实现了Runnable与Future接口,然后在构造函数中还要注入Callable对象(或者变形的Callable对象:Runnable + Result),所以FutureTask类既可以使用new Thread(Runnable r)放到一个新线程中跑,也可以使用ExecutorService.submit(Runnable r)放到线程池中跑,而且两种方式都可以获取返回结果,但实质是一样的,即如果要有返回结果那么构造函数一定要注入一个Callable对象,或者注入一个Runnable对象加一个预先给定的结果 

public class FutureTask<V> implements RunnableFuture<V>

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

FutureTask提供了2个构造器,可以看到其实实现方式是一样的

public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;
}

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;
}

事实上,FutureTask只是Future接口的实现类。

使用示例

1.使用Callable+Future获取执行结果

ExecutorService threadPool =  Executors.newSingleThreadExecutor();
Future<String> future =
    threadPool.submit(
        new Callable<String>() {
            public String call() throws Exception {
                Thread.sleep(1000);
                return "hello";
            };
        }
);
System.out.println("....");
try {
    System.out.println("get value:" + future.get());
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (Exception e) {
    e.printStackTrace();
}

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

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

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class TestThreadPool {
    public static void main(String[] args) {
        // 创建线程池
        ExecutorService threadPool =  Executors.newFixedThreadPool(3);
        List<FutureTask<String>> tasks = new ArrayList<FutureTask<String>>();
        for (int i = 0; i < 10; i++) {
            FutureTask<String> futureTask = new FutureTask<String>(new ThreadPoolTask(i));
            threadPool.submit(futureTask);
            tasks.add(futureTask);
        }  
        for (FutureTask<String> futureTask : tasks) {
            try {
                // 阻塞一直等待执行完成拿到结果
                System.out.println("future result:" + futureTask.get());
                // 阻塞一直等待执行完成拿到结果,如果在超时时间内,没有拿到则抛出异常
                // System.out.println("future result:"+futureTask.get(1,TimeUnit.SECONDS));
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } // 捕获超时异常
            // catch (TimeoutException e) {
            // e.printStackTrace();
            // }
        }      
        System.out.println("--------------------------");
        threadPool.shutdown();
    }

    public static class ThreadPoolTask implements Callable<String> {
        private int value;
        public ThreadPoolTask(int value) {
            this.value = value;
        }
        public String call() throws Exception {
            System.out.println("value-----" + value++);
            Thread.sleep(2000);
            return String.valueOf(value);
        }
    }
}

对比使用Callable+Future的实现可知道,使用FutureTask不用去接收submit的返回值,而是自身就继承了Future,相对方便些。

CompletionService

示例

这里先给出个示例

import java.util.Random;
import java.util.concurrent.*;


public class CallableAndFuture {
        ExecutorService threadPool2 =  Executors.newFixedThreadPool(10);
        CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(threadPool2);
        for(int i=1;i<=10;i++){
            final int seq = i;
            completionService.submit(new Callable<Integer>() {
                public Integer call() throws Exception {
                    Thread.sleep(new Random().nextInt(2000));
                    return seq;
                }
            });
        }
        for(int i=0;i<10;i++){
            try {
                System.out.println(
                        completionService.take().get());
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ExecutionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println("======");
        threadPool2.shutdown();
    }

}

使用Callable+Future和FutureTask的实现需要定义一个List来保存Future的值,取值得时候还需要通过for循环遍历,相对来说使用CompletionService更方便。


CompletionService方法

ExecutorCompletionService构造方法:

1、ExecutorCompletionService(Executor executor) 使用为执行基本任务而提供的执行程序创建一个 ExecutorCompletionService,并将 LinkedBlockingQueue 作为完成队列。

2、ExecutorCompletionService(Executor executor, BlockingQueue<Future<V>>>completionQueue) 使用为执行基本任务而提供的执行程序创建一个 ExecutorCompletionService,并将所提供的队列作为其完成队列。

返回值方法
Future<V> take()获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。
Future<V> submit(Callable task)提交要执行的值返回任务,并返回表示挂起的任务结果的 Future。
Future<V> submit(Runnable task, V result)提交要执行的 Runnable 任务,并返回一个表示任务完成的 Future,可以提取或轮询此任务。
Future<V> poll()获取并移除表示下一个已完成任务的 Future,如果不存在这样的任务,则返回 null。
Future<V> poll(long timeout, TimeUnit unit)获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则将等待指定的时间(如果有必要)。

原理

CompletionService整合了Executor和BlockingQueue的功能。你可以将Callable任务提交给它去执行,然后使用类似于队列中的take和poll方法,在结果完整可用时获得这个结果,像一个打包的Future。ExecutorCompletionService是实现CompletionService接口的一个类,并将计算任务委托给一个Executor。

ExecutorCompletionService的实现相当直观。它在构造函数中创建一个BlockingQueue,用它去保持完成的结果。计算完成时会调用FutureTask中的done方法。当提交一个任务后,首先把这个任务包装为一个QueueingFuture,它是 FutureTask的一个子类,然后覆写done方法,将结果置入BlockingQueue中,take和poll方法委托给了BlockingQueue,它会在结果不可用时阻塞。

ExecutorCompletionService的一个构造函数,整合了Executor和BlockingQueue的功能:

public ExecutorCompletionService(Executor executor) {
    if (executor == null)
        throw new NullPointerException();
    this.executor = executor;
    this.aes = (executor instanceof AbstractExecutorService) ?
        (AbstractExecutorService) executor : null;
    this.completionQueue = new LinkedBlockingQueue<Future<V>>();
}

任务的提交和执行都是委托给Executor来完成。当提交某个任务时,该任务首先将被包装为一个QueueingFuture,

public Future<V> submit(Callable<V> task) {  
       if (task == null) throw new NullPointerException();  
       RunnableFuture<V> f = newTaskFor(task);  
       executor.execute(new QueueingFuture(f));  
        return f; 
}

 

QueueingFuture是FutureTask的一个子类,通过改写该子类的done方法,可以实现当任务完成时,将结果放入到BlockingQueue中。

private class QueueingFuture extends FutureTask<Void> {
    QueueingFuture(RunnableFuture<V> task) {
        super(task, null);
        this.task = task;
    }
    protected void done() { completionQueue.add(task); }
    private final Future<V> task;
}

 

而通过使用BlockingQueue的take或poll方法,则可以得到结果。在BlockingQueue不存在元素时,这两个操作会阻塞,一旦有结果加入,则立即返回。public Future<V> take() throws InterruptedException {

    return completionQueue.take();
}

public Future<V> poll() {
    return completionQueue.poll();
}

转载:java并发编程-Callable与Future

posted on 2018-07-09 22:47  pony1223  阅读(1390)  评论(0编辑  收藏  举报

导航