【springboot】闲话 springboot 的几种异步机制 及 长轮询的概念和简单实现

本文主要是介绍【springboot】闲话 springboot 的几种异步机制 及 长轮询的概念和简单实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 引子
  • springboot的几种异步形式
    • 开启异步支持和线程池配置(重要)
    • 第一种:@Async
    • 第二种:Callable<T>
    • 第三种:WebAsyncTask<T>
    • 第四种:DeferredResult<T>
  • 长轮询的简单实现
    • 概念
    • 实现
      • 服务端
      • 客户端

引子

在聊 springboot 的异步机制之前,我们先要搞清楚一个最基础的概念:什么是同步?什么是异步?
其实这个概念理解起来很简单,假设有任务 A 和任务 B,如果必须先完成 A,再去做 B 则可称为同步。而如果在完成 A 的同时,还可以同时去做 B,两个事情互不影响,则完成完成任务 B 的过程可称为异步执行。

springboot提供了四种异步方式。

  • 第一种:@Async,被标记了此注解的方法会被丢到异步线程中执行,不会影响接口的返回。
  • 第二种:接口返回 Callable类型。此类型对于调用方来说感知上还是同步的,只是后台服务启用了一个异步线程执行而已。
  • 第三种:接口返回WebAsyncTask类型,此类型和返回Callable的逻辑大体相同,也只是后台服务启用了一个异步线程执行而已。 但是多了一些使用的自由度,比如自定义超时时间,使用自定义线程池等,下面会详细介绍。
  • 第四种:接口返回DeferredResult类型,有别于第一种不影响接口返回,和第二、三种待任务执行完成后返回结果,DeferredResult类型则可以将请求挂起,可以通过其他线程设置返回值,我们也会基于这个来实现长轮询。

springboot的几种异步形式

开启异步支持和线程池配置(重要)

首先开启异步支持:@EnableAsync,如下:

@EnableAsync
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}

其实对于Callable、WebAsyncTask和DeferredResult来说,不开启此配置也可以用(@Async 不行,必须开启才能用,否则不生效)。但是为什么推荐大家开启呢?主要是因为线程池的原因,在默认情况下Callable、WebAsyncTask都需要使用到线程池,但是如果没有使用@EnableAsync开启异步支持,那么他们都是使用SimpleAsyncTaskExecutor,轻看它的几句源码:

protected void doExecute(Runnable task) {Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));thread.start();
}

看到没,随时使用随时创建,用这玩意如果异步太多不把系统资源耗尽了,所以不推荐大家用。而一旦使用@EnableAsync开启异步支持,那么就会默认创建一个线程池。大家可以这么配置,这样就安全多了(当然还可以通过代码配置,这里就不具体说了,只为了让大家知道有这么回事,闲聊嘛,不想太累,而且网上一大把,大家可以自己搜索)

spring:task:execution:pool:core-size: 10

这里主要配置一个核心线程数,其他参数大家可以自己研究下,无非就那几个,这里只为告知其存在。当然生产环境大家都会按需配置,所以大家还是配置一下最为稳妥。

@EnableAsync开启异步支持后,默认情况下:@Async、Callable、WebAsyncTask都使用上面配置的线程池。后续就不再介绍了。

第一种:@Async

此种类型的异步,更多是在主线程完成了主要任务之后,将一些剩余工作比如记录日志,或者通知其他系统的工作交由异步线程来完成。可以如下方式实现:此时调用接口会立马返回 finish,在 10 秒后,后台会打印“异步执行完毕”

@GetMapping("/test-async")
public String testAsync(){asyncService.async();return "finish";
}@Service
public class AsyncService {@Asyncpublic void async() {try {Thread.sleep(10000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("异步执行完毕");}
}

第二种:Callable

此种类型的存在,主要是为了提升系统的并发能力。我们知道每一个请求 servlet容器(比如 tomcat) 都会为其分配一个处理线程,但这个线程是有数量限制的,一旦被用完就会限制系统其他请求的接入。所以通过将请求放到其他线程池中处理的方式来提升系统的并发能力。例如:

@GetMapping("test-callable")
public Callable<String> testCallable(){Callable<String> callable = new Callable<String>() {@Overridepublic String call() throws Exception {log.info("任务开始callable");Thread.sleep(10000);log.info("任务完成callable");return "任务执行完成";}};return callable;
}

最后接口会返回:任务执行完成

第三种:WebAsyncTask

此类型和Callable异曲同工,只是更加灵活,所以推荐大家使用WebAsyncTask。

 @GetMapping("test-webasynctask")public WebAsyncTask<String> testWebAsyncTask() {WebAsyncTask<String> webAsyncTask = new WebAsyncTask<>(20000, new Callable<String>() {@Overridepublic String call() throws Exception {log.info("任务开始webasynctask");Thread.sleep(10000);log.info("任务完成webasynctask");return "任务执行完成";}});webAsyncTask.onError(null);webAsyncTask.onCompletion(null);webAsyncTask.onTimeout(null);return webAsyncTask;}

看到没,有好多回调!还支持超时机制!更加灵活,再看下WebAsyncTask的构造函数:

public WebAsyncTask(Callable<V> callable);
public WebAsyncTask(long timeout, Callable<V> callable);
public WebAsyncTask(@Nullable Long timeout, String executorName, Callable<V> callable);
public WebAsyncTask(@Nullable Long timeout, AsyncTaskExecutor executor, Callable<V> callable);

我们发现,它还支持传入线程池!是不是很灵活!

第四种:DeferredResult

此类型同样是会将请求挂起,待合适的时机再返回!什么是合适的时机?我们先来看一个例子。

@GetMapping("test-deferredResult")
public DeferredResult<String> testDeferredResult(){DeferredResult<String> deferredResult = new DeferredResult<>();asyncService.setDeferredResult(deferredResult);return deferredResult;
}@Async
public void setDeferredResult(DeferredResult<String> deferredResult) {try {log.info("任务开始");Thread.sleep(10000);//设置返回!!!!!deferredResult.setResult("DeferredResult执行成功啦!");log.info("任务完成");} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("异步执行完毕");
}

我们看到,在异步线程中通过deferredResult.setResult(T)的方法即可让接口返回!这里我们是借用了@Async 方法,其实这个异步线程可以是任意的其他线程,或者其他请求触发。即只要有一个地方通过deferredResult.setResult(T)设置了返回即可!是不是很酷!

同样,我们再来看看它的其他方法,首先是构造函数(比较简单,就不详细描述了):

public DeferredResult();
public DeferredResult(Long timeoutValue);
public DeferredResult(@Nullable Long timeoutValue, Object timeoutResult);
public DeferredResult(@Nullable Long timeoutValue, Supplier<?> timeoutResult)

再看其他方法:

//是否已经过期或者设置过了结果
boolean setOrExpired = deferredResult.isSetOrExpired();
//获取已经设置的结果
Object result = deferredResult.getResult();
//是否已经设置了结果
boolean hasResult = deferredResult.hasResult();
//这个的 ErrorResult可以是Exception或者Throwable,会当做异常被抛出
deferredResult.setErrorResult(null);
//会对 Result 进一步处理
deferredResult.setResultHandler(null);
//完成回调
deferredResult.onCompletion();
//错误回调
deferredResult.onError();
//超时回调
deferredResult.onTimeout();

长轮询的简单实现

概念

这里简单介绍一下概念:所谓长轮询,其实是一种实时通信技术。即当客户端连接到服务器时,服务器不会立马返回,而是将请求挂起,当有数据(某事件发生,或者数据发生变更)时再返回,这样就达到了实时通信的效果。当然一般客户端和服务端都会有超时时间的存在,当请求超时时,客户端再次发起请求来保持连接。

实现

这里我们基于DeferredResult来实现一个简单的长轮询。这里举一个例子:客户端监控是否有配置变更。如下:

服务端

这里:客户端通过请求test-detect-config-refresh来监听配置变化,而test-set-config接口则用于更新配置。当配置更新时,会响应所有的客户端的请求,达到更新配置的目的!

List<DeferredResult<String>> deferredResults = new ArrayList<>();@GetMapping("test-detect-config-refresh")
public DeferredResult<String> testDetectConfigRefresh() {DeferredResult<String> deferredResult = new DeferredResult<>(10000L);deferredResult.onTimeout(() -> {deferredResults.remove(deferredResult);deferredResult.setResult("empty");});deferredResults.add(deferredResult);return deferredResult;
}@GetMapping("test-set-config")
public String testDetectConfigRefresh(String config) {for (DeferredResult<String> deferredResult : deferredResults) {if (deferredResult.isSetOrExpired()) {continue;}deferredResult.setResult(config);}deferredResults.clear();return "success";
}

客户端

注意客户端的超时时间要大于服务器的超时时间!不然客户端先超时,服务器端还咋返回呢?对吧!

while(true){String result = request("test-detect-config-refresh",20000);if ("empty".equals(result)) {//空返回,再次发起,不做任何处理} else {//更新配置updateConfig(result);}
}private String request(String url, long timeout){//实现请求
}private void updateConfig(String config){//实现更新配置
}

是不是很简单!当然这里只是介绍,存在一些问题,比如,如果恰好配置更新时,客户端超时断开了怎么办?实际生产还是需要更为完备的设计!这里就不过多介绍了,大家可以自行思考!

这篇关于【springboot】闲话 springboot 的几种异步机制 及 长轮询的概念和简单实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/864385

相关文章

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

AJAX请求上传下载进度监控实现方式

《AJAX请求上传下载进度监控实现方式》在日常Web开发中,AJAX(AsynchronousJavaScriptandXML)被广泛用于异步请求数据,而无需刷新整个页面,:本文主要介绍AJAX请... 目录1. 前言2. 基于XMLHttpRequest的进度监控2.1 基础版文件上传监控2.2 增强版多

Spring Security方法级安全控制@PreAuthorize注解的灵活运用小结

《SpringSecurity方法级安全控制@PreAuthorize注解的灵活运用小结》本文将带着大家讲解@PreAuthorize注解的核心原理、SpEL表达式机制,并通过的示例代码演示如... 目录1. 前言2. @PreAuthorize 注解简介3. @PreAuthorize 核心原理解析拦截与

一文详解JavaScript中的fetch方法

《一文详解JavaScript中的fetch方法》fetch函数是一个用于在JavaScript中执行HTTP请求的现代API,它提供了一种更简洁、更强大的方式来处理网络请求,:本文主要介绍Jav... 目录前言什么是 fetch 方法基本语法简单的 GET 请求示例代码解释发送 POST 请求示例代码解释

Java图片压缩三种高效压缩方案详细解析

《Java图片压缩三种高效压缩方案详细解析》图片压缩通常涉及减少图片的尺寸缩放、调整图片的质量(针对JPEG、PNG等)、使用特定的算法来减少图片的数据量等,:本文主要介绍Java图片压缩三种高效... 目录一、基于OpenCV的智能尺寸压缩技术亮点:适用场景:二、JPEG质量参数压缩关键技术:压缩效果对比

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

Redis分片集群的实现

《Redis分片集群的实现》Redis分片集群是一种将Redis数据库分散到多个节点上的方式,以提供更高的性能和可伸缩性,本文主要介绍了Redis分片集群的实现,具有一定的参考价值,感兴趣的可以了解一... 目录1. Redis Cluster的核心概念哈希槽(Hash Slots)主从复制与故障转移2.

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

使用Python实现一键隐藏屏幕并锁定输入

《使用Python实现一键隐藏屏幕并锁定输入》本文主要介绍了使用Python编写一个一键隐藏屏幕并锁定输入的黑科技程序,能够在指定热键触发后立即遮挡屏幕,并禁止一切键盘鼠标输入,这样就再也不用担心自己... 目录1. 概述2. 功能亮点3.代码实现4.使用方法5. 展示效果6. 代码优化与拓展7. 总结1.

Mybatis 传参与排序模糊查询功能实现

《Mybatis传参与排序模糊查询功能实现》:本文主要介绍Mybatis传参与排序模糊查询功能实现,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、#{ }和${ }传参的区别二、排序三、like查询四、数据库连接池五、mysql 开发企业规范一、#{ }和${ }传参的