0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

异步编程的几种种实现方式(上)

jf_78858299 来源:微观技术 作者:Tom哥 2023-02-15 16:15 次阅读

早期的系统是同步的,容易理解,我们来看个例子

同步编程

图片

当用户创建一笔电商交易订单时,要经历的业务逻辑流程还是很长的,每一步都要耗费一定的时间,那么整体的RT就会比较长。

于是,聪明的人们开始思考能不能将一些非核心业务从主流程中剥离出来,于是有了异步编程雏形。

异步编程是让程序并发运行的一种手段。它允许多个事件同时发生,当程序调用需要长时间运行的方法时,它不会阻塞当前的执行流程,程序可以继续运行。

图片

核心思路:采用多线程优化性能,将串行操作变成并行操作。异步模式设计的程序可以显著减少线程等待,从而在高吞吐量场景中,极大提升系统的整体性能,显著降低时延。

接下来,我们来讲下异步有哪些编程实现方式

一、线程 Thread

直接继承 Thread类 是创建异步线程最简单的方式。

首先,创建Thread子类,普通类或匿名内部类方式;然后创建子类实例;最后通过start()方法启动线程。

public class AsyncThread extends Thread{
    @Override
    public void run() {
        System.out.println("当前线程名称:" + this.getName() + ", 执行线程名称:" + Thread.currentThread().getName() + "-hello");
    }
}
public static void main(String[] args) {

  // 模拟业务流程
  // .......
  
    // 创建异步线程 
    AsyncThread asyncThread = new AsyncThread();

    // 启动异步线程
    asyncThread.start();
}

当然如果每次都创建一个 Thread线程,频繁的创建、销毁,浪费系统资源。我们可以采用线程池

@Bean(name = "executorService")
public ExecutorService downloadExecutorService() {
    return new ThreadPoolExecutor(20, 40, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2000),
            new ThreadFactoryBuilder().setNameFormat("defaultExecutorService-%d").build(),
            (r, executor) -> log.error("defaultExecutor pool is full! "));
}

将业务逻辑封装到 RunnableCallable 中,交由 线程池 来执行

图片

二、Future

上述方式虽然达到了多线程并行处理,但有些业务不仅仅要执行过程,还要获取执行结果。

Java 从1.5版本开始,提供了 CallableFuture,可以在任务执行完毕之后得到任务执行结果。

当然也提供了其他功能,如:取消任务、查询任务是否完成等

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

方法描述:

  • cancel():取消任务,如果取消任务成功返回true,如果取消任务失败则返回false
  • isCancelled():表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true
  • isDone():表示任务是否已经完成,如果完成,返回true
  • get():获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回
  • get(long timeout, TimeUnit unit):用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null

代码示例:

public class CallableAndFuture {

    public static ExecutorService executorService = new ThreadPoolExecutor(4, 40,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue(1024), new ThreadFactoryBuilder()
            .setNameFormat("demo-pool-%d").build(), new ThreadPoolExecutor.AbortPolicy());


    static class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "异步处理,Callable 返回结果";
        }
    }

    public static void main(String[] args) {
        Future future = executorService.submit(new MyCallable());
        try {
            System.out.println(future.get());
        } catch (Exception e) {
            // nodo
        } finally {
            executorService.shutdown();
        }
    }
}

Future 表示一个可能还没有完成的异步任务的结果,通过 get 方法获取执行结果,该方法会阻塞直到任务返回结果。

三、FutureTask

FutureTask 实现了 RunnableFuture 接口,则 RunnableFuture 接口继承了 Runnable 接口和 Future 接口,所以可以将 FutureTask 对象作为任务提交给 ThreadPoolExecutor 去执行,也可以直接被 Thread 执行;又因为实现了 Future 接口,所以也能用来获得任务的执行结果。

FutureTask 构造函数:

public FutureTask(Callable callable)
public FutureTask(Runnable runnable, V result)

FutureTask 常用来封装 CallableRunnable,可以作为一个任务提交到线程池中执行。除了作为一个独立的类之外,也提供了一些功能性函数供我们创建自定义 task 类使用。

FutureTask 线程安全由CAS来保证。

ExecutorService executor = Executors.newCachedThreadPool();
// FutureTask包装callbale任务,再交给线程池执行
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
    System.out.println("子线程开始计算:");
    Integer sum = 0;
    for (int i = 1; i <= 100; i++)
        sum += i;
    return sum;
});

// 线程池执行任务, 运行结果在 futureTask 对象里面
executor.submit(futureTask);

try {
    System.out.println("task运行结果计算的总和为:" + futureTask.get());
} catch (Exception e) {
    e.printStackTrace();
}
executor.shutdown();

Callable 和 Future 的区别:Callable 用于产生结果,Future 用于获取结果

图片

如果是对多个任务多次自由串行、或并行组合,涉及多个线程之间同步阻塞获取结果,Future 代码实现会比较繁琐,需要我们手动处理各个交叉点,很容易出错。

四、异步框架 CompletableFuture

Future 类通过 get() 方法阻塞等待获取异步执行的运行结果,性能比较差。

JDK1.8 中,Java 提供了 CompletableFuture 类,它是基于异步函数式编程。相对阻塞式等待返回结果,CompletableFuture 可以通过回调的方式来处理计算结果,实现了异步非阻塞,性能更优。

优点

  • 异步任务结束时,会自动回调某个对象的方法
  • 异步任务出错时,会自动回调某个对象的方法
  • 主线程设置好回调后,不再关心异步任务的执行

泡茶示例:

图片

(内容摘自:极客时间的《Java 并发编程实战》)

//任务1:洗水壶->烧开水
CompletableFuture<Void> f1 =
        CompletableFuture.runAsync(() -> {
            System.out.println("T1:洗水壶...");
            sleep(1, TimeUnit.SECONDS);

            System.out.println("T1:烧开水...");
            sleep(15, TimeUnit.SECONDS);
        });

//任务2:洗茶壶->洗茶杯->拿茶叶
CompletableFuture f2 =
        CompletableFuture.supplyAsync(() -> {
            System.out.println("T2:洗茶壶...");
            sleep(1, TimeUnit.SECONDS);

            System.out.println("T2:洗茶杯...");
            sleep(2, TimeUnit.SECONDS);

            System.out.println("T2:拿茶叶...");
            sleep(1, TimeUnit.SECONDS);
            return "龙井";
        });

//任务3:任务1和任务2完成后执行:泡茶
CompletableFuture f3 =
        f1.thenCombine(f2, (__, tf) -> {
            System.out.println("T1:拿到茶叶:" + tf);
            System.out.println("T1:泡茶...");
            return "上茶:" + tf;
        });

//等待任务3执行结果
System.out.println(f3.join());

}

CompletableFuture 提供了非常丰富的API,大约有50种处理串行,并行,组合以及处理错误的方法。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 编程
    +关注

    关注

    88

    文章

    3642

    浏览量

    94095
  • 程序
    +关注

    关注

    117

    文章

    3798

    浏览量

    81501
  • 异步
    +关注

    关注

    0

    文章

    62

    浏览量

    18115
收藏 人收藏

    评论

    相关推荐

    Spring Boot如何实现异步任务

    Spring Boot 提供了多种方式实现异步任务,这里介绍三种主要实现方式。 1、基于注解 @Async @Async 注解是 Spri
    的头像 发表于 09-30 10:32 1494次阅读

    USART异步工作方式编程

    USART异步工作方式编程 串行通信的接收有查询和中断2种方式,在实际应用中,一般不采用查询接收数据,常用的是中断接收数据。发送有中断发送和非中断发送,在下面的例程中我们采用了中断接收
    发表于 09-08 09:48

    【我是电子发烧友】低功耗设计的最佳编程模型:异步编程

    异步编程可以编写出速度快、资源省的高效程序,可以在单线程环境下实现高并发,可以在没有操作系统的情况下实现TCP/IP等协议栈。又快又省可以将功耗控制在最低水平,因此
    发表于 04-29 20:30

    有哪几种方式可以通过Keil模块化编程实现流水灯设计?

    Keil的模块化编程是什么?有哪几种方式可以通过Keil模块化编程实现流水灯设计?如何对流水灯设计进行Proteus仿真?
    发表于 07-14 07:17

    芯片在线编程方式有哪些

    电路板编程,在线编程功能随即流行起来。在线编程目前有ICP、ISP、IAP方式,各方式都是
    发表于 11-18 08:54

    几种最基本的通讯方式解释与总结

    几种最基本的通讯方式解释与总结出发点在读书的时,对于几种通讯总是一知半解,没有刻意的去学习使用,直到工作后,才发现,对于知识的应用不能只是一知半解,要想对其熟练应用,就必须理解透彻。如果文中有写的有
    发表于 02-23 07:30

    相机曝光模式有哪几种种类?如何设置曝光模式?

    相机曝光模式有哪几种种类?如何设置曝光模式?
    发表于 03-02 09:34

    异步收发通信端口(UART)的FPGA实现

    文章介绍了一种在现场可编程门阵列(FPGA)实现UART 的方法。首先阐述了UART 异步串行通信原理,然后介绍了实现UART
    发表于 08-06 16:24 55次下载

    异步传输方式的HDLC协议的实现

    研究实现了一种 HDLC (High Level Data Link Contr01)协议的改进方法,该方法把HDLC协议传统的同步传榆方式改成了异步传输方式,既保留了原有HDLC协议
    发表于 07-20 17:25 62次下载
    <b class='flag-5'>异步</b>传输<b class='flag-5'>方式</b>的HDLC协议的<b class='flag-5'>实现</b>

    快速改变滤波器中心频率的几种实现方式

    快速改变滤波器中心频率的几种实现方式,下来看看
    发表于 01-07 21:24 12次下载

    在Python中实现异步编程(附源码)

    异步编程是并行编程的一种方式。单个工作单元独立于主应用程序线程运行,并通知调用线程其完成、失败情况或进度。下面这张图理解起来会更直观一些:
    的头像 发表于 10-27 14:36 2403次阅读
    在Python中<b class='flag-5'>实现</b><b class='flag-5'>异步</b><b class='flag-5'>编程</b>(附源码)

    异步编程几种种实现方式(下)

    除了硬编码的异步编程处理方式,SpringBoot 框架还提供了 `注解式` 解决方案,以 `方法体` 为边界,方法体内部的代码逻辑全部按异步方式
    的头像 发表于 02-15 16:15 725次阅读
    <b class='flag-5'>异步</b><b class='flag-5'>编程</b>的<b class='flag-5'>几种种</b><b class='flag-5'>实现</b><b class='flag-5'>方式</b>(下)

    三相异步电动机的几种调速方式

    介绍了几种调速的方式
    发表于 10-07 11:18 0次下载

    java实现多线程的几种方式

    Java实现多线程的几种方式 多线程是指程序中包含了两个或以上的线程,每个线程都可以并行执行不同的任务或操作。Java中的多线程可以提高程序的效率和性能,使得程序可以同时处理多个任务。 Java提供
    的头像 发表于 03-14 16:55 799次阅读

    笼形异步电机常用的降压启动方式

    ,保护电机和电网。 笼形异步电机降压启动方式概述 笼形异步电机在启动时,由于转子的惯性和负载的影响,需要较大的启动转矩。为了减少启动电流,通常采用以下几种降压启动
    的头像 发表于 09-03 15:18 2217次阅读