Java | 一分钟掌握异步编程 | 4 - Future异步

作者:Mars酱

声明:本文章由Mars酱编写,部分内容来源于网络,如有疑问请联系本人。

转载:欢迎转载,转载前先请联系我!

前言

前篇 Java | 一分钟掌握异步编程 | 3 - 线程异步 - 掘金 (juejin.cn) 介绍了实现多线程异步处理业务的方式,但是我们无法获取到线程返回的结果,这是在JDK 5之前Java版本的小小缺陷,在Java 5之后,增加了Future机制。

上车Future

Java 5之后使用CallableFuture,可以产生结果 和 拿到结果,改造接单代码:

/**
 * @author mars酱
 */
// 去掉下单依赖的thread部分
public class OrderThread /*extends Thread*/ {
    Integer deskNum;

    public OrderThread(Integer deskNum) {
        this.deskNum = deskNum;
    }

//    @Override
    public void run() {
        System.out.println(">> 老板: " + deskNum + "号桌已下单,开工做面!");
    }
}
复制代码

添加一个Callable的接口实现,用来返回结果:

import java.util.concurrent.Callable;

/**
 * @author mars酱
 */
public class OrderThreadCallable implements Callable<String> {
    Integer deskNum;

    public OrderThreadCallable(Integer deskNum) {
        this.deskNum = deskNum;
    }

    @Override
    public String call() throws Exception {
        new OrderThread(deskNum).run();
        return ">> " + deskNum + "号桌,取餐啦";
    }
}
复制代码

餐馆主函数也改造下:

/**
 * @author mars酱
 */
public class NoodlesRestaurant {
    public static void main(String[] args) {
//        System.out.println("你:老板,两碗面条。(潇洒扫一扫,6元巨款两碗面)");
//        OrderThread order = new OrderThread();
//        order.start();
//
//        System.out.println("你:我俩等下吃碗面,就去看个电影,然后再那个###酒店吧");
//        System.out.println("女友:你好坏坏");


//        ExecutorService executorService = Executors.newFixedThreadPool(1, null);
//        Executors.newCachedThreadPool();
//        Executors.newSingleThreadExecutor();

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,
                10,
                5,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(),
                new ThreadPoolExecutor.CallerRunsPolicy());

//        for (int i = 0; i < 10; i++) {
//            System.out.println(i + "桌 | 潇洒扫一扫~");
//            threadPoolExecutor.execute(new OrderThread(i));
//            System.out.println(i + "桌 | 点餐完成!");
//        }
        
        // 座位号
        Integer deskNum = 1;

        System.out.println(deskNum + "号桌:潇洒扫一扫,6元两碗面!");
        Future<String> future = threadPoolExecutor.submit(new OrderThreadCallable(1));
        try {
            String result = future.get();
            System.out.println(result);
            System.out.println(deskNum + "号桌:端面上桌,快乐食面。");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        } finally {
            threadPoolExecutor.shutdown();
        }
    }
}
复制代码

运行后的结果:

看到没?已经可以通知出结果了,食客可以听到叫号再去取餐,完全不耽误桌上的活。我们来模拟大量接客吧。

开门接客

改造一下餐馆主函数即可:

import java.util.concurrent.*;

/**
 * @author mars酱
 */
public class NoodlesRestaurant {
    public static void main(String[] args) {
//        System.out.println("你:老板,两碗面条。(潇洒扫一扫,6元巨款两碗面)");
//        OrderThread order = new OrderThread();
//        order.start();
//
//        System.out.println("你:我俩等下吃碗面,就去看个电影,然后再那个###酒店吧");
//        System.out.println("女友:你好坏坏");


//        ExecutorService executorService = Executors.newFixedThreadPool(1, null);
//        Executors.newCachedThreadPool();
//        Executors.newSingleThreadExecutor();

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,
                10,
                5,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(),
                new ThreadPoolExecutor.CallerRunsPolicy());

//        for (int i = 0; i < 10; i++) {
//            System.out.println(i + "桌 | 潇洒扫一扫~");
//            threadPoolExecutor.execute(new OrderThread(i));
//            System.out.println(i + "桌 | 点餐完成!");
//        }

        // 座位号
//        Integer deskNum = 1;
        for (int i = 0; i < 10; i++) {
            Integer deskNum = i;
            System.out.println(deskNum + "号桌:潇洒扫一扫!");
            Future<String> future = threadPoolExecutor.submit(new OrderThreadCallable(deskNum));
            try {
                String result = future.get();
                System.out.println(result);
                System.out.println(deskNum + "号桌:面上桌,快乐食。");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } catch (ExecutionException e) {
                throw new RuntimeException(e);
            }
        }

        threadPoolExecutor.shutdown();
    }
}
复制代码

注意,特意在最后调用了shutdown()方法,调用这个方法表示线程也不再接受新的任务。如果不接受任务还想等待现有的任务执行完成,就调用awaitTermination()方法。上面的代码执行结果:

客人需求多,厨师要如厕

假设有对情侣饭量很好,点了好多菜品,厨师也不是机器人,吃喝拉撒也要有,我们模拟下这种情况,会出现什么样的效果:

假设是3号桌的客人点菜多,轮到这个桌的时候,厨师刚好憋不出要上厕所了,改造下代码:

/**
 * @author mars酱
 */
public class OrderThread /*extends Thread*/ {
    Integer deskNum;

    public OrderThread(Integer deskNum) {
        this.deskNum = deskNum;
    }

    //    @Override
    public void run() {
        System.out.println(">> 老板: " + deskNum + "号桌已下单,开工做面!");
        // 只有3号桌的菜品多
        if (deskNum == 3) {
            System.out.println(">> | 捉鱼,杀鱼,炖鱼");
            System.out.println(">> | 捉虾, 杀虾, 油焖虾");
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(">> | 伙夫还去上了个厕所~");
        }
    }

}
复制代码

再次运行,发现在输出“>> | 捉虾, 杀虾, 油焖虾” 之后等待了10秒

然后再从“ | 伙夫还去上了个厕所~”开始,输出后续的。

最后

看来Future对于结果的获取也不是很友好,虽然相比线程池异步提升了很大的执行效率,但是结果的获取遇到计算量大的情况只能通过阻塞或者轮询的方式得到任务的结果。

Future.get() 会在线程获取结果之前get方法会一直阻塞,但是它提供了一个isDone()方法,可以去检查这个结果是否已经完成。

但是,尽管这样,异步阻塞式的设计和我们现实中的还是有差距,如果我点餐了,总要去看看有没有做好,或者出餐台总给我检查下有没有做好,而且其他做好的餐也要排队等着出餐的话,那就太耗费资源了。

再最后

虽然 Future 并不如意,但是“天生我材必有用”,它的实现类FutureTask在执行多任务计算的时利用FutureTask 和 ExecutorService 组合方式提交计算任务,主线程可以继续执行其他任务,当主线程需要子线程的计算结果时,再异步获取子线程的执行结果。

而且,FutureTask 能确保任务只执行一次,在很多高并发场景下,我们只需要某些任务只执行一次的时候,那么使用 Future 的具体实现类 FutureTask 就恰好能胜任了。

猜你喜欢

转载自juejin.im/post/7218571281393287229