Java并发编程(一)--Executor,Callable,Future和FutureTask

说到多线程编程,首先想到的大概是Thread和Runnable,自己平时用的比较多的也是这两个。不过他们有一个缺点就是无法返回执行结果。如果想要获取,就要使用一些其他的方法,比如线程间通信,总之比较麻烦。
Java在1.5开始引入了Executor框架的一系列相关的内容,包括的Callable和Future就解决了前面提到的无法返回结果的问题。Executors类还提供了一系列的方法来生成线程池,来提高多线程编程时的效率。
这篇文章就先记录一下关于Executor,Callable,Future和FutureTask的学习内容。

Executor

先来说说Executor,这是一个接口

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

内容很简单,只有一个execute方法,参数是一个Runnable。从它的注释就可以看出来,Executor并没又规定一定会异步执行这个Runnable,而是取决于我们的实现。如果按照下面这样写

Executor executor = new Executor() {
    @Override
    public void execute(Runnable command) {
        command.run();
    }
};
executor.execute(new Runnable() {
    @Override
    public void run() {

    }
});

显然,在调用execute方法后,Runnable的run方法就会马上在调用的线程中执行了。和直接执行Runnable的run方法没有什么区别。
Executor的作用就是将它的调用和执行方式分离开来,提交到同一个Executor的任务的执行方式必然是一致的。如果需要异步操作,也不需要每次去写new Thread再调用start。看下面代码

class MyExecutor implements Executor {
    @Override
    public void execute(Runnable command) {
        new Thread(command).start();
    }
}

这样的话,之后我只要调用MyExecutor.execute(runnable),就可以新建一个线程执行,并且可以复用,统一方式处理。

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

其实他和Runnable很像,只不过把run方法改成了call,并且有了返回值,可以看到是一个泛型(泛型我还没有完全搞懂,之前以为很简单,看了Effective Java之后感觉又糊涂了。有时间要再研究一下),这样就可以把执行的结果返回出去。
Callable一般结合ExecutorService使用,后面会给出用法。

Future

这个接口代码注释比较长就不贴了,可以直接去看源码。它的作用就是结合Callable或者Runnable来使用,定义了五个方法

boolean cancel(boolean mayInterruptIfRunning);

取消执行,成功返回true,失败则返回false。如果任务已经完成了、被取消了或者因为一些原因无法取消,则会失败。如果任务尚未执行,再调用cancel后则永远不会再执行了。如果任务正在执行,就要看参数mayInterruptIfRunning,如果为true,就会中断执行;如果为false,则会执行完这个任务。

boolean isCancelled();

任务有没有被成功取消

boolean isDone();

任务是否完成

V get() throws InterruptedException, ExecutionException;

获取执行结果。这个方法会阻塞调用的线程直到任务完成并返回结果。

V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;

获取执行结果,同样会阻塞调用的线程,如果在给的时间内没有执行完,则会返回null。
这是一个接口,具体实现我们可以看一下FutureTask。

FutureTask

FutureTask实际上实现的是RunnableFuture,而RunnableFuture同时实现了Runnable和Future。实现了各个方法的逻辑,具体可以看源码。

例子

public class Test {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);
            //1
            FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
            //        Future future = executor.submit(futureTask);
            Future future = executor.submit(futureTask, 3);
            //2
            Future future1 = executor.submit(new MyCallable());

            executor.shutdown();
            try {
                Log.d("testexecutor--", "future: " + future.get());
                Log.d("testexecutor--", "futureTask:" + futureTask.get());
                Log.d("testexecutor--", "future1: " + future1.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
    }

        class MyCallable implements Callable<Integer> {

            @Override
            public Integer call() throws Exception {
                int result = 0;
                for (int i = 0; i < 100; i++){
                    result += i;
                }
                return result;
            }
        }
}

定义了一个MyCallable继承了Callable,做的事情就是从0到99叠加,最后返回结果。然后使用ExecutorService生成了一个线程池(线程池的具体用法留到下篇文章),之后用了两种方法像里面添加任务,第一种是submit一个FutureTask,第二种是直接submit Callable,两者都可以达成像线程池中添加任务的目的。
区别在于通过Future获得结果的方式。我们通过代码也可以看得出,executor的submit其实有三种重载的方法

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

参数是Callable的方法也就是对应例子中第2种方法,这种方法通过submit方法返回的Future可以直接get到结果。而第1种方法中用到的两个submit的重载,需要通过传入的FutureTask的get来获取结果。调用参数为Runnable的submit返回的Future的get,得到的是null,而使用两个参数的第二种重载,get得到的就是第二个参数传入的内容。所以如果运行这个例子,打出来的log如下

future: 3
futureTask:4950
future1: 4950

为什么前两种submit的重载方法返回得到的Future不能直接调用get来获得结果呢?看源码可以知道,是源码中直接写死了这个返回,第一种submit就是直接返回null,而第二种就直接返回了第二个参数。这三种方法最后都会生成一个FutureTask对象,不同的是因为前两种传入的都是Runnable,所以调用的是这个构造及后面的流程

public <T> Future<T> submit(Runnable task, T result) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task, result);
    execute(ftask);
    return ftask;
}

protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}

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

这个callable方法最后会执行传入的runnable,并返回result。

而第三种submit的重载传入的是一个Callable

public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
  return new FutureTask<T>(callable);
}

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

直接返回了这个FutureTask对象。所以直接对这个submit返回的Future调用get,也就是和例子中第一种方法,对FutureTask调用get一样的,都是调用了FutureTask的run,在run中就会得到返回值了。

参考大神文章:
http://www.cnblogs.com/dolphin0520/p/3949310.html
http://www.iteye.com/topic/366591
http://blog.youkuaiyun.com/linghu_java/article/details/17123057

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值