我们在多线程编程中最常用的两种方式:一种是直接继承Thread,另外一种就是实现Runnable接口。这两种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。


如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。


我们今天来看看第三种创建线程的方式:

而自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。


我们今天从下面这四个知识点讲今天的内容


一. Callable与Runnable

二. Future

三. FutureTask

四. 使用示例


一.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方法里面的参数类型就是Callable。

暂时只需要知道Callable一般是和ExecutorService配合来使用的,具体的使用方法讲在后面讲述。


一般情况下我们使用第一个submit方法和第三个submit方法,第二个submit方法很少使用。

二. 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个方法,下面依次解释每个方法的作用:


1. cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。

参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。


如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;


如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;


如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。


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


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


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


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


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

1)判断任务是否完成;

2)能够中断任务;

3)能够获取任务执行结果。


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


三. FutureTask


我们先来看一下FutureTask的实现:





public class FutureTask< V>implements Runnable,Future< V>{    ...}


FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现:





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


可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。


FutureTask提供了2个构造器:




public FutureTask(Callable< V>callable) {}public FutureTask(Runnable runnable, V result) {}


事实上,FutureTask是Future接口的一个唯一实现类。


java 创建两个线程同时调用接口 java两种创建线程方式_java 创建两个线程同时调用接口


四.使用示例


先上一个场景:假如你突然想做饭,但是没有厨具,也没有食材。网上购买厨具比较方便,食材去超市买更放心。


实现分析:在快递员送厨具的期间,我们肯定不会闲着,可以去超市买食材。所以,在主线程里面另起一个子线程去网购厨具。


但是,子线程执行的结果是要返回厨具的,而run方法是没有返回值的。所以,这才是难点,需要好好考虑一下。





public class CommonCook {    public static void main(String[] args) throws InterruptedException {        long startTime = System.currentTimeMillis();        // 第一步 网购厨具        OnlineShopping thread = new OnlineShopping();        thread.start();        thread.join();  // 保证厨具送到        // 第二步 去超市购买食材        Thread.sleep(2000);  // 模拟购买食材时间        Shicai shicai = new Shicai();        System.out.println("第二步:食材到位");        // 第三步 用厨具烹饪食材        System.out.println("第三步:开始展现厨艺");        cook(thread.chuju, shicai);                System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");    }        // 网购厨具线程    static class OnlineShopping extends Thread {                private Chuju chuju;        @Override        public void run() {            System.out.println("第一步:下单");            System.out.println("第一步:等待送货");            try {                Thread.sleep(5000);  // 模拟送货时间            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("第一步:快递送到");            chuju = new Chuju();        }            }    //  用厨具烹饪食材    static void cook(Chuju chuju, Shicai shicai) {}        // 厨具类    static class Chuju {}        // 食材类    static class Shicai {}}


运行结果:

第一步:下单

第一步:等待送货

第一步:快递送到

第二步:食材到位

第三步:开始展现厨艺

总共用时7013ms


可以看到,多线程已经失去了意义。在厨具送到期间,我们不能干任何事。对应代码,就是调用join方法阻塞主线程。


有人问了,不阻塞主线程行不行???

不行!!!


从代码来看的话,run方法不执行完,属性chuju就没有被赋值,还是null。换句话说,没有厨具,怎么做饭。


Java现在的多线程机制,核心方法run是没有返回值的;如果要保存run方法里面的计算结果,必须等待run方法计算完,无论计算过程多么耗时。


面对这种尴尬的处境,程序员就会想:在子线程run方法计算的期间,能不能在主线程里面继续异步执行???


Where there is a will,there is a way!!!


这种想法的核心就是Future模式,下面先应用一下Java自己实现的Future模式。





public class FutureCook {    public static void main(String[] args) throws InterruptedException, ExecutionException {        long startTime = System.currentTimeMillis();        // 第一步 网购厨具        CallableonlineShopping = new Callable() {            @Override            public Chuju call() throws Exception {                System.out.println("第一步:下单");                System.out.println("第一步:等待送货");                Thread.sleep(5000);  // 模拟送货时间                System.out.println("第一步:快递送到");                return new Chuju();            }                    };        FutureTasktask = new FutureTask(onlineShopping);        new Thread(task).start();        // 第二步 去超市购买食材        Thread.sleep(2000);  // 模拟购买食材时间        Shicai shicai = new Shicai();        System.out.println("第二步:食材到位");        // 第三步 用厨具烹饪食材        if (!task.isDone()) {  // 联系快递员,询问是否到货            System.out.println("第三步:厨具还没到,心情好就等着(心情不好就调用cancel方法取消订单)");        }        Chuju chuju = task.get();        System.out.println("第三步:厨具到位,开始展现厨艺");        cook(chuju, shicai);                System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");    }        //  用厨具烹饪食材    static void cook(Chuju chuju, Shicai shicai) {}        // 厨具类    static class Chuju {}        // 食材类    static class Shicai {}}


运行结果:

第一步:下单

第一步:等待送货

第二步:食材到位

第三步:厨具还没到,心情好就等着(心情不好就调用cancel方法取消订单)

第一步:快递送到

第三步:厨具到位,开始展现厨艺

总共用时5005ms

 

可以看见,在快递员送厨具的期间,我们没有闲着,可以去买食材;而且我们知道厨具到没到,甚至可以在厨具没到的时候,取消订单不要了。



陛下...看完奏折,点个赞再走吧!