Java多线程之Callable、Future和FutureTask

隔了一个月没有更新,主要是因为一方面年底事情比较忙,另一方面是主要忙着更新GitHub。

其实很久之前就用过Callable、Future和FutureTask了,在我的GitHub上面也可以找到,现在趁年后有空总结总结。

多线程基础Runnable

关于Java多线程编程基础可以参考:
http://blog.csdn.net/maxwell_nc/article/details/44872089
上述文章主要讲述使用继承方式和实现Runnable接口来实现多线程编程。
上面两种方法要想得到线程执行的结果必须使用监听器的方式或者其他线程通信方式,实现起来比较麻烦。

JDK 1.5之后,java.util.concurrent包中提供了Callable、Future和FutureTask,我们逐个来看看。

Callable接口

首先看看Callable接口的源代码:

public interface Callable<V> {
    V call() throws Exception;
}

从源码可以看出这是一个泛型接口,与Runnable不一样,它没有run方法,只有一个call方法,而且call方法是带返回值的。泛型就是需要返回的值类型。
和Runnable使用方法不相同,由于需要获取返回值,所以需要池来管理线程,一般需要配合ExecutorService来使用。

ExecutorService接口

也是先看看源代码:

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    ...//部分无关方法省略
}

ExecutorService实现的Executor接口源码:

public interface Executor {
    void execute(Runnable command);
}

要想使用ExecutorService,可以使用Executors工具类,简单来说可以使用newSingleThreadExecutor、newCachedThreadPool、newFixedThreadPool方法来创建线程池。
由于ExecutorService整体比较复杂,使用Callable和Future不需要了解太多,所以本篇省略此部分,后续有空补全。
只需要知道:ExecutorService身上的sumbit方法可以把任务提交到线程池并且获取结果。

Future接口

也是先看看源代码:

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

由于篇幅问题,所以把注释都删除了,可以参考下原来的注释部分,有详细解析每个方法,我简单地总结下每个方法:

  • boolean cancel(boolean mayInterruptIfRunning);
    尝试取消执行任务,如果已经完成或者已经取消则会失败,如果还没有开始则取消了不会再开始。
    如果任务执行中,由参数mayInterruptIfRunning来决定是否中断任务。

  • boolean isCancelled();
    如果任务完成之前被取消则返回真

  • boolean isDone();
    任务完成返回真,要注意的是:任务被正常中断、发生异常、被取消都会返回真

  • V get() throws InterruptedException, ExecutionException;
    等待任务完成并获取返回值

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

    等待任务完成并获取返回值,参数timeout是最长等待时间,参数unit是timeout的单位,超时则抛出TimeoutException

Callable和Future组合使用实例

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;


public class Test {

    public static void main(String[] args) throws Exception {

        Task task = new Task();

        //创建线程池
        ExecutorService executor = Executors.newCachedThreadPool();
        //提交任务
        Future<String> result = executor.submit(task);
        //关闭线程池
        executor.shutdown();

        //get方法阻塞主线程,要获取到结果才会继续执行
        System.out.println(result.get());
        System.out.println("主线程获取到结果");
    }

}

class Task implements Callable<String>{

    @Override
    public String call() throws Exception {
        Thread.sleep(3000);
        return "result string";
    }

}

上述代码的执行结果为:

3秒后在控制台顺序打印
result string
主线程获取到结果

FutureTask接口

最后要看看FutureTask,FutureTask主要用来包装Callable或者是Runnable对象,本文不讨论Runnable。先看看源代码:

public class FutureTask<V> implements RunnableFuture<V> {

    private volatile int state;
    private Callable<V> callable;


    public FutureTask(Callable<V> callable) {
        //...
        this.callable = callable;
        //...
    }

    public V get() throws InterruptedException, ExecutionException {
        //...
    }

    public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        //...
    }

    protected void done() { }
}

由于FutureTask比较复杂,只抽取了与本文相关的部分。
可以看出FutureTask和Future都有相似的get方法。
FutureTask是Future和Callable的结合体,FutureTask封装了线程的状态,如果有多个任务执行的时候,使用FutureTask十分方便,在任务完成后会直接执行done方法。

由于FutureTask封装了线程的状态,所以不需要借助ExecutorService来管理线程。
上面的实例代码可以改成下面的:

//使用FutureTask包装Callable任务
FutureTask<String> futureTask = new FutureTask<>(task);
//FutureTask实际继承了Runnale,可以直接使用Thread开启
new Thread(futureTask).start();

//get方法阻塞主线程,要获取到结果才会继续执行
System.out.println(futureTask.get());

FutureTask使用实例

使用FutureTask多半是因为要执行多个任务,由于身上带有回调方法done,接下来用一个例子来说明FutureTask常用方法:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

public class Test {

    public static void main(String[] args) throws Exception {

        //创建线程池
        ExecutorService executor = Executors.newCachedThreadPool();

        //提交5个任务
        for (int i = 0; i < 5; i++) {
            //创建任务
            Task task = new Task();
            //包装Callable任务
            MyFutureTask mTask = new MyFutureTask(task);
            //提交任务
            executor.submit(mTask);
        }

        //关闭线程池
        executor.shutdown();

        System.out.println("主线程运行到此");
    }

}

class MyFutureTask extends FutureTask<String> {

    /** * 传入要包装的任务 * * @param callable * 要包装的任务 */
    public MyFutureTask(Callable<String> callable) {
        super(callable);
    }

    /** * 任务完成后自动执行 */
    @Override
    protected void done() {
        try {
            // 利用get方法获取结果
            System.out.println(get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

class Task implements Callable<String> {

    @Override
    public String call() throws Exception {
        Thread.sleep(3000);
        return "result string";
    }

}

上述代码的执行结果为:

主线程运行到此
约3秒后在控制台打印
result string
result string
result string
result string
result string

这里可以看出,使用FutureTask不再需要阻塞主线程,主线程可以继续执行。

参考文章

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

声明

原创文章,欢迎转载,请保留出处。
有任何错误、疑问或者建议,欢迎指出。
我的邮箱:[email protected]

你可能感兴趣的:(多线程,并发,callable,future,FutureTask)