了解Callable和Spring DeferredResult

1.简介

Servlet 3.0中引入的异步支持提供了在另一个线程中处理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);
    }
}

所有这些示例的源代码都可以在Github Spring-Rest仓库中找到

2.从阻塞控制器开始

在此示例中,请求到达控制器。 只有执行了长时间运行的方法并且退出@RequestMapping带注释的方法,该servlet线程才会被释放。

@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上运行此示例,查看日志,可以看到直到处理了长时间运行的任务(5秒后)后,才释放servlet请求:

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,而不是直接返回结果:

@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将在另一个线程中调用Callable中定义的任务。 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;
    }

那么,与Callable有什么区别? 所不同的是这次线程是由我们管理的。 在不同的线程中设置DeferredResult的结果是我们的责任。

在此示例中,我们要做的是使用CompletableFuture创建一个异步任务。 这将创建一个新线程,将在其中执行长时间运行的任务。 在此线程中,我们将设置结果。

我们从哪个池中检索这个新线程? 默认情况下,CompletableFuture中的supplyAsync方法将在ForkJoin池中运行任务。 如果要使用其他线程池,可以将执行程序传递给supplyAsync方法:

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

如果运行此示例,我们将得到与Callable相同的结果:

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做同样的事情,即释放容器线程并在另一个线程中异步处理长时间运行的任务。 区别在于谁管理执行任务的线程。

我正在Google Plus和Twitter上发布我的新帖子。 如果您要更新新内容,请关注我。

翻译自: https://www.javacodegeeks.com/2015/07/understanding-callable-and-spring-deferredresult.html

`Callable` 和 `Runnable` 是 Java 编程中用于实现多线程的接口。 1. `Runnable` 接口是在 Java 1.0 中引入的。它定义了一个抽象方法 `run()`,该方法在新线程中执行。可以通过实现 `Runnable` 接口来创建一个可运行的任务,然后将其传递给 `Thread` 类的构造函数来创建一个新线程。 示例代码: ```java public class MyRunnable implements Runnable { public void run() { // 在这里编写线程的逻辑 } } // 创建线程 Thread thread = new Thread(new MyRunnable()); thread.start(); ``` 2. `Callable` 接口是在 Java 1.5 中引入的。它也定义了一个抽象方法 `call()`,但与 `Runnable` 不同的是,`call()` 方法可以返回结果,并且可以抛出异常。可以通过实现 `Callable` 接口来创建一个可调用的任务,并使用 `ExecutorService` 的 `submit()` 方法来提交任务并获取其返回值。 示例代码: ```java import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class MyCallable implements Callable<String> { public String call() throws Exception { // 在这里编写任务的逻辑 return "Task completed"; // 返回结果 } } // 创建线程池 ExecutorService executor = Executors.newFixedThreadPool(1); // 提交任务并获取 Future 对象 Future<String> future = executor.submit(new MyCallable()); // 获取任务执行结果 String result = future.get(); ``` 总结: - `Runnable` 接口适用于那些不需要返回结果的简单任务。 - `Callable` 接口适用于那些需要返回结果的任务,并且可以抛出异常。 - `Runnable` 接口的方法是 `void` 类型的,而 `Callable` 接口的方法可以指定返回类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值