1-介绍

Servlet 3中的异步支持为在另一个线程中处理HTTP请求提供了可能性。当有一个长时间运行的任务时,这是特别有趣的,因为当另一个线程处理这个请求时,容器线程被释放,并且可以继续为其他请求服务。

这个主题已经解释了很多次,Spring框架提供的关于这个功能的类似乎有一点混乱——在一个Controller中返回Callable 和 DeferredResult。

在这篇文章中,我将实施这两个例子,以显示其差异。

这里所显示的所有示例都包括执行一个控制器,该控制器将执行一个长期运行的任务,然后将结果返回给客户机。长时间运行的任务由taskservice处理:

@Service
public class TaskServiceImpl implements TaskService {
private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override
public String execute() {
try {
Thread.sleep(5000);
logger.info("Slow task executed");
return "Task finished";
} catch (InterruptedException e) {
throw new RuntimeException();
}
}
}

这个web应用是用Spring Boot创建的,我们将执行下面的类来运行我们的例子:

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

2-阻塞的Controller

在这个例子中,一个请求到达控制器。servlet线程不会被释放,直到长时间运行的方法被执行,我们退出@requestmapping注释的方法。

@RestController
public class BlockingController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final TaskService taskService; @Autowired
public BlockingController(TaskService taskService) {
this.taskService = taskService;
} @RequestMapping(value = "/block", method = RequestMethod.GET, produces = "text/html")
public String executeSlowTask() {
logger.info("Request received");
String result = taskService.execute();
logger.info("Servlet thread released"); return result;
}
}

如果我们运行这个例子http://localhost:8080/block,在日志里我们会发现servlet request不会被释放,直到长时间的任务执行完(5秒后)。

2015-07-12 12:41:11.849  [nio-8080-exec-6] x.s.web.controller.BlockingController    : Request received
2015-07-12 12:41:16.851 [nio-8080-exec-6] x.spring.web.service.TaskServiceImpl : Slow task executed
2015-07-12 12:41:16.851 [nio-8080-exec-6] x.s.web.controller.BlockingController : Servlet thread released

3-返回Callable

在这个例子中,不是直接返回的结果,我们将返回一个Callable:

@RestController
public class AsyncCallableController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final TaskService taskService; @Autowired
public AsyncCallableController(TaskService taskService) {
this.taskService = taskService;
} @RequestMapping(value = "/callable", method = RequestMethod.GET, produces = "text/html")
public Callable<String> executeSlowTask() {
logger.info("Request received");
Callable<String> callable = taskService::execute;
logger.info("Servlet thread released"); return callable;
}
}

返回Callable意味着Spring MVC将调用在不同的线程中执行定义的任务。Spring将使用TaskExecutor来管理线程。在等待完成的长期任务之前,servlet线程将被释放。

2015-07-12 13:07:07.012  [nio-8080-exec-5] x.s.w.c.AsyncCallableController          : Request received
2015-07-12 13:07:07.013 [nio-8080-exec-5] x.s.w.c.AsyncCallableController : Servlet thread released
2015-07-12 13:07:12.014 [ MvcAsync2] x.spring.web.service.TaskServiceImpl : Slow task executed

你可以看到我们在长时间运行的任务执行完毕之前就已经从servlet返回了。这并不意味着客户端收到了一个响应。与客户端的通信仍然是开放的等待结果,但接收到的请求的线程已被释放,并可以服务于另一个客户的请求。

4-返回DeferredResult

首先,我们需要创建一个deferredresult对象。此对象将由控制器返回。我们将完成和Callable相同的事,当我们在另一个线程处理长时间运行的任务的时候释放servlet线程。

@RestController
public class AsyncDeferredController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final TaskService taskService; @Autowired
public AsyncDeferredController(TaskService taskService) {
this.taskService = taskService;
} @RequestMapping(value = "/deferred", method = RequestMethod.GET, produces = "text/html")
public DeferredResult<String> executeSlowTask() {
logger.info("Request received");
DeferredResult<String> deferredResult = new DeferredResult<>();
CompletableFuture.supplyAsync(taskService::execute)
.whenCompleteAsync((result, throwable) -> deferredResult.setResult(result));
logger.info("Servlet thread released"); return deferredResult;
}
}

所以,返回DeferredResult和返回Callable有什么区别?不同的是这一次线程是由我们管理。创建一个线程并将结果set到DeferredResult是由我们自己来做的。

用completablefuture创建一个异步任务。这将创建一个新的线程,在那里我们的长时间运行的任务将被执行。也就是在这个线程中,我们将set结果到DeferredResult并返回。

是在哪个线程池中我们取回这个新的线程?默认情况下,在completablefuture的supplyasync方法将在forkjoin池运行任务。如果你想使用一个不同的线程池,你可以通过传一个executor到supplyasync方法:

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

如果我们运行这个例子,我们将得到如下结果:

2015-07-12 13:28:08.433  [io-8080-exec-10] x.s.w.c.AsyncDeferredController          : Request received
2015-07-12 13:28:08.475 [io-8080-exec-10] x.s.w.c.AsyncDeferredController : Servlet thread released
2015-07-12 13:28:13.469 [onPool-worker-1] x.spring.web.service.TaskServiceImpl : Slow task executed

5-结论

站在一定高度来看这问题,Callable和Deferredresult做的是同样的事情——释放容器线程,在另一个线程上异步运行长时间的任务。不同的是谁管理执行任务的线程。

文中涉及的代码spring-rest

翻译自Xavier Padró's Blog

理解Callable 和 Spring DeferredResult(翻译)的更多相关文章

  1. 【转】理解Callable 和 Spring DeferredResult

    http://www.cnblogs.com/aheizi/p/5659030.html 1-介绍 Servlet 3中的异步支持为在另一个线程中处理HTTP请求提供了可能性.当有一个长时间运行的任务 ...

  2. Spring DeferredResult 异步请求

    Spring DeferredResult 异步请求 一.背景 二.分析 三.实现要求 四.后端代码实现 五.运行结果 1.超时操作 2.正常操作 六.DeferredResult运行原理 六.注意事 ...

  3. spring技术翻译开始

    从今天开始,我会坚持每天花费两个小时来翻译一本英文书(当然自己觉得绝对算得上是经典),可能我英文水平有限,但也请路过的高人予以指点. 如果有翻译的出入很大,望各位告知,本人一定更改.决定翻译的目的有两 ...

  4. 入门级:理解FAT32文件系统(转载翻译)

    FAT(File Allocation Table ) 这个网页的目的是帮助你理解怎么样在微软FAT32文件系统下取得数据,处理的硬盘的大小通常在500M到几百G之间.FAT是一个相对简单和纯净的文件 ...

  5. Spring简单的小例子SpringDemo,用于初略理解什么是Spring以及JavaBean的一些概念

    一.开发前的准备 两个开发包spring-framework-3.1.1.RELEASE-with-docs.zip和commons-logging-1.2-bin.zip,将它们解压,然后把Spri ...

  6. Memcached理解笔2---XMemcached&Spring集成

    一.Memcached Client简要介绍 Memcached Client目前有3种: Memcached Client for Java SpyMemcached XMemcached 这三种C ...

  7. 深入理解Callable接口

    Callable接口: Callable,新启线程的一种方式,返回结果并且可能抛出异常的任务,在前面的新启线程的文章中用过,但是没有具体讲解 优点: 可以获取线程的执行结果,也称为返回值 通过与Fut ...

  8. HowToDoInJava Spring 教程·翻译完成

    原文:HowToDoInJava 协议:CC BY-NC-SA 4.0 欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远. ApacheCN 学习资源 目录 Spring 5 Spr ...

  9. Spring Web Async异步处理#Callable #DeferredResult

    Spring MVC 对于异步请求处理的两种方式 场景: Tomcat对于主线程性能瓶颈,当Tomcat请求并发数过多时,当线程数满时,就会出现请求等待Tomcat处理,这个时候可以使用子线程处理业务 ...

随机推荐

  1. humanoid ik unity 配件 animation的问题

    遇到这样一个问题 想给角色设置ik 以实现代码控制的 更为自然的 角色动作 比如角色头跟随点击转动 身体也有相应扭转 https://docs.unity3d.com/Manual/InverseKi ...

  2. idea 设置jetty进程jvm参数

    /** * MyEclipse6.5通过Jetty跑Web应用时提示OutOfMemoryError: PermGen space的解决办法 * @see ---------------------- ...

  3. json.parse()使用过程中,肯能会出现的问题(Excel表中数据也存在类似问题)

    如果使用json.parse()报如下的错误, Js错误: Jsp页面的显示的错误: 经分析显示:是由于某个字段的值存在换行符造成的 解决方案:只需要将存在换行符的字段重新编辑,使其不存在换行符即可. ...

  4. 【大盛】全网首发HTC One/M7 最新本地化TrickDroid9.0/固件升级/永久root/高级,快速设置/稳定,流畅经典ROM

    了解更多请关注:点击打开链接 ROM版本 HTC One/M7_TrickDroid9.0.0 ROM作者 雪狼团队-大盛   http://weibo.com/DaShengdd Android版本 ...

  5. Android 网络请求框架Retrofit

    Retrofit是Square公司开发的一款针对Android网络请求的框架,Retrofit2底层基于OkHttp实现的,OkHttp现在已经得到Google官方认可,大量的app都采用OkHttp ...

  6. SugarCE问题点记录

    问:如何获取module参数?如果module参数不存在,如何处理?答:首先检查$_REQUEST['module'],然后再检查$sugar_config['default_module']是否有设 ...

  7. 老生常谈:DEDECMS安全设置问题分享

    Dedecms安全问题已经是老生常谈了.虽然无忧主机php虚拟主机安全性是很高的,但是黑客总是利用dedecms的漏洞进行注入.其实有些时候,是我们对dedecms的安全设置不到位导致的.今天无忧主机 ...

  8. easyui treegrid增、删、改及批量保存

    treegrid添加新行和删除行的方法: 添加和删除直接调用这两个方法即可,修改的方法有点特殊,treegrid没有提供update方法,设置行为编辑状态,$('#obj_tgd_objectlist ...

  9. C#实现播放声音的方法

    文章来自学IT网:http://www.xueit.com/html/2009-09/21_4598_00.html 在这里介绍使用C#实现播放声音的几种方法,都是利用组件等方法来实现的,有兴趣的话可 ...

  10. jsp版本的环境变量集合

    System.out.println("Protocol: " + request.getProtocol());System.out.println("Scheme: ...