从同步编程到异步编程的过程

前言

我们都知道要想提高系统的吞吐量,就一定离不开异步编程的思想,无论是平时项目中接触到的MQ、响应式编程还是操作系统里的AIO、epoll本质上都是在利用异步的方式,提升系统的并行度,本文通过一个简单的小案例来看看如何利用Java中的Futrue完成从同步到异步的转变。

场景介绍

我们举一个简单的小场景,用户购物,假设就分为三步:1、查看库存是否充足 2、下单支付 3、扣减库存

第一版

这是一种典型的同步阻塞流程,整个流程执行时间等于所有流程的总和。

/**
 * DEMO01:完全基于同步阻塞的方式完成
 */
public class Demo_01 {

    public static void main(String[] args) {
        long s = System.currentTimeMillis();
        // 查询库存是否充足
        checkStoreEnough();
        // 生成订单
        createOrder();
        // 扣减库存
        updateStore();
        long e = System.currentTimeMillis();
        System.out.println("耗时:" + (e - s));
    }

    private static void checkStoreEnough() {
        System.out.println("查询库存是否充足");
        ThreadUtils.sleep(1000);
    }

    private static void createOrder() {
        System.out.println("生成订单");
        ThreadUtils.sleep(2000);
    }

    private static void updateStore() {
        System.out.println("更新库存");
        ThreadUtils.sleep(1000);
    }

}

第二版

对于第一版的存在的问题,我们很快想到了使用多线程的方式,这样整个流程的执行时间就等于子流程中执行时间最长的。

/**
 * DEMO02: 开始基于多线程的方式,整个过程都异步化,但是并不满足业务上的要求,库存充足与生成订单应该有依赖关系,只当库存充足时才能生成订单
 *
 * 为了演示方式,没有用线程池
 */
public class Demo_02 {

    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(3);
        long s = System.currentTimeMillis();
        // 查询库存是否充足
        new Thread(() -> {
            checkStoreEnough();
            countDownLatch.countDown();
        }).start();
        // 生成订单
        new Thread(() -> {
            createOrder();
            countDownLatch.countDown();
        }).start();
        // 扣减库存
        new Thread(() -> {
            updateStore();
            countDownLatch.countDown();
        }).start();
        countDownLatch.await();
        long e = System.currentTimeMillis();
        System.out.println("耗时:" + (e - s));
    }

    private static void checkStoreEnough() {
        System.out.println("查询库存是否充足");
        ThreadUtils.sleep(1000);
    }

    private static void createOrder() {
        System.out.println("生成订单");
        ThreadUtils.sleep(2000);
    }

    private static void updateStore() {
        System.out.println("更新库存");
        ThreadUtils.sleep(1000);
    }

}

第三版

第二版虽然通过多线程的方式实现了流程异步化,但却不能满足业务上要求,能够生成订单应该要根据前一步查询库存是否充足的结果来判断,如果库存足够才会生成订单,否则就不生成订单。如果我们引入了Future,通过get方法,等待结果的返回。


/**
 * DEMO03: 使用Future的方式,在Future.get()的时候阻塞等待返回结果,但似乎又回到了同步模式
 */
public class Demo_03 {

    public static void main(String[] args) throws Exception {
        final CountDownLatch countDownLatch = new CountDownLatch(2);
        long s = System.currentTimeMillis();
        // 查询库存是否充足
        Callable<Integer> ca1 = () -> {
            checkStoreEnough();
            return 1;
        };
        FutureTask<Integer> ft1 = new FutureTask<>(ca1);
        new Thread(ft1).start();
        // 库存充足,调用生成订单的方法
        if (ft1.get() == 1) {
            new Thread(() -> {
                // 生成订单
                createOrder();
                countDownLatch.countDown();
            }).start();
            new Thread(() -> {
                // 扣减库存
                updateStore();
                countDownLatch.countDown();
            }).start();
            countDownLatch.await();
            long e = System.currentTimeMillis();
            System.out.println("耗时:" + (e - s));
        }
    }

    private static void checkStoreEnough() {
        System.out.println("查询库存是否充足");
        ThreadUtils.sleep(1000);
    }

    private static void createOrder() {
        System.out.println("生成订单");
        ThreadUtils.sleep(2000);
    }

    private static void updateStore() {
        System.out.println("更新库存");
        ThreadUtils.sleep(1000);
    }

}

第四版

假设我现在第三步更新库存也要依赖生成订单成功,那就变成了现在这样,你会发现我们用到future,但依然是同步阻塞的模式。


/**
 * DEMO04:如果更新库存也依赖生成订单,也要等待future.get()方法,那就完全回到了同步阻塞的方式。
 */
public class Demo_04 {

    public static void main(String[] args) throws Exception {
        long s = System.currentTimeMillis();
        // 查询库存是否充足
        Callable<Integer> c1 = () -> {
            checkStoreEnough();
            return 1;
        };
        FutureTask<Integer> f1 = new FutureTask<>(c1);
        new Thread(f1).start();
        // 库存充足,调用生成订单的方法
        if (f1.get() == 1) {
            Callable<Integer> c2 = () -> {
                createOrder();
                return 1;
            };
            FutureTask<Integer> f2 = new FutureTask<>(c2);
            new Thread(f2).start();
            // 生成订单成功,调用更新库存的方法
            if (f2.get() == 1) {
                updateStore();
            }
        }
        long e = System.currentTimeMillis();
        System.out.println("耗时:" + (e - s));
    }

    private static void checkStoreEnough() {
        System.out.println("查询库存是否充足");
        ThreadUtils.sleep(1000);
    }

    private static void createOrder() {
        System.out.println("生成订单");
        ThreadUtils.sleep(2000);
    }

    private static void updateStore() {
        System.out.println("更新库存");
        ThreadUtils.sleep(1000);
    }

}

第五版

我们在主线程开始就让整个流程异步出去,基于一种通知、回调的思想,过程中的每一步都以通知的方式通知下游接口,上下游之间没有强依赖的关系,这样就使的主流程(也就是接用户请求的入口)不受限于每一步的处理时间。


/**
 * DEMO05:主线程异步处理,相当于发出一个通知后,对于主线程而言就结束了,接下来的事情让各个消费者去消费就行了。
 * 之后每一步过程也是如此,从局部看每一步耗时并不会减少,但从整体上看,整个过程的吞吐量将会大大提升。
 */
public class Demo_05 {

    public static void main(String[] args) {
        long s = System.currentTimeMillis();
        // 主线程收到请求后,执行异步出去,对于主线程来说就结束了。
        new Thread(() -> checkStoreEnough()).start();
        long e = System.currentTimeMillis();
        System.out.println("耗时:" + (e - s));
    }

    private static void checkStoreEnough() {
        Callable<Integer> c1 = () -> {
            System.out.println("查询库存是否充足");
            ThreadUtils.sleep(1000);
            return 1;
        };
        FutureTask<Integer> f1 = new FutureTask<>(c1);
        new Thread(f1).start();
        // 将Future传给生成订单的逻辑,自己就结束了
        createOrder(f1);
    }

    private static void createOrder(FutureTask<Integer> f1) {
        try {
            if (f1.get() == 1) {
                Callable<Integer> c2 = () -> {
                    System.out.println("生成订单");
                    ThreadUtils.sleep(2000);
                    return 1;
                };
                FutureTask<Integer> f2 = new FutureTask<>(c2);
                new Thread(f2).start();
                // 将Future传给更新库存的逻辑,自己就结束了
                updateStore(f2);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private static void updateStore(FutureTask<Integer> f2) {
        try {
            if (f2.get() == 1) {
                System.out.println("更新库存");
                ThreadUtils.sleep(1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

总结

第五版的功能实现只是为了提现异步的实现逻辑,对于先判断库存再生成订单再更新库存肯定会出现问题,但是不影响异步化的思想。比如我们可以先创建一个预生成的订单,然后判断库存充足后,再通过乐观锁的方式更新库存,最后确认库存更新成功后再通知订单更改状态。

总之,通过异步的方式不但能够大大提升系统的整体吞吐量,还能实现业务的解耦,当我们遇到类似上面这样的业务场景,应该要有异步化的思维方式。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码拉松

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值