这可能是阿粉见过最详细的一份 Spring 异步任务教程(上)

简介: 阿粉最近碰到一个场景,用户注册之后需要发送邮件给其邮箱。原先设计中,这是一个同步过程,注册方法需要等待邮件发送成功才能返回。由于邮件发送流程对于注册来说并不是一个关键节点,我们可以将邮件发送异步执行,减少注册方法执行时间。

阿粉最近碰到一个场景,用户注册之后需要发送邮件给其邮箱。原先设计中,这是一个同步过程,注册方法需要等待邮件发送成功才能返回。

由于邮件发送流程对于注册来说并不是一个关键节点,我们可以将邮件发送异步执行,减少注册方法执行时间。

我们可以自己创建线程池,然后执行异步任务,示例代码如下:

// 生产使用线程池的最佳实践,一定要自定义线程池,不要嫌麻烦,使用 Executors 创建线程池
private ThreadPoolExecutor threadPool =
        new ThreadPoolExecutor(5,
                10,
                60l,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(200),
                new ThreadFactoryBuilder().setNameFormat("register-%d").build());
/**
 * 使用线程池执行发送邮件的任务
 */
private void sendEmailByThreadPool() {
    threadPool.submit(() -> emailService.sendEmail());
}

ps: 生产使用线程池的最佳实践,一定要自定义线程池,根据业务场景设置合理的线程池参数,另外给线程设置具有明确意义的前缀,这样排查问题就非常简单。

千万不要为了方便,使用 Executors 相关方法创建线程池。

上面代码中使用线程池完成了发送邮件的异步任务,可以看到这个示例还是有点麻烦,我们不仅要自定义线程池,还需要在创建相关任务执行类。

Spring 提供执行异步任务功能,我们使用一个注解就可以轻松完成上面的功能。

今天阿粉就来讲解一下如何使用 Spring 异步任务,以及 Spring 异步任务使用过程中一些注意点。

异步任务使用方式

Spring 异步任务需要在相关的方法上设置 @Async 注解,这里为了举例,我们创建一个 EmailService 类,专用完成邮件服务。

代码如下所示:

@Slf4j
@Service
public class EmailService {
    /**
     * 异步发送任务
     *
     * @throws InterruptedException
     */
    @SneakyThrows
    @Async
    public void sendEmailAsync() {
        log.info("使用 Spring 异步任务发送邮件示例");
        // 模拟邮件发送耗时
        TimeUnit.SECONDS.sleep(2l);
    }
}

这里要注意了,Spring 异步任务默认关闭的,我们需要使用 @EnableAsync开启异步任务。

如果还在使用 Spring XML 配置,我们需要配置如下配置:

<task:annotation-driven/>

上述配置完成之后,我们只需要在调用方,比如上一层 Controller 注入这个 EmailService ,然后直接调用这个方法,该方法将会在异步线程中执行。

@Slf4j
@RestController
public class RegisterController {
    @Autowired
    EmailService emailService;
    @RequestMapping("register")
    public String register() {
     log.info("注册流程开始");
     emailService.sendEmailAsync();
        return "success";
    }
 }

输出日志如下:

从日志上可以看到,两个方法执行线程不一样,这就说明了EmailService#sendEmailAsync 被异步线程成功执行。

带有返回值的异步任务

上面的异步任务比较简单,但是有时我们有需要获取异步任务返回值。

如果使用线程池执行异步任务,我们可以使用 threadPool#submit 获取返回对象 Future,接着我们就可以调用其内 get 方法,获取返回结果。

在 Spring 异步任务中,我们也可以使用 Future 获取返回结果,示例代码如下:

@Async
@SneakyThrows
public Future<String> sendEmailAsyncWithResult() {
    log.info("使用 Spring 异步任务发送邮件,并且获取任务返回结果示例");
    TimeUnit.SECONDS.sleep(2l);
    return AsyncResult.forValue("success");
}

由于公号不能直接点击外链,关注「Java极客技术」,回复「20200701」获取源码~

这里需要注意,这里返回对象我们需要使用 Spring 内部类 AsyncResult

Controller 层调用代码如下所示:

private void sendEmailWithResult() {
        Future<String> future = emailService.sendEmailAsyncWithResult();
        try {
            String result = future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

我们知道 Future#get 方法将会一直阻塞,直到异步任务执行成功。

有时候我们获取异步任务的返回值是为了做一下后续业务,但是主流程方法是无需返回异步任务的返回值。如果我们使用了 Future#get方法,主流程就会一直被阻塞。

对于这种场景,我们可以使用 org.springframework.util.concurrent.ListenableFuture稍微改造一下上面的方法。

ListenableFuture 这个类允许我们注册回调函数,一旦异步任务执行成功,或者执行异常,将会立刻执行回调函数。通过这种方式就可以不用阻塞执行的主线程。

示例代码如下:

@Async
@SneakyThrows
public ListenableFuture<String> sendEmailAsyncWithListenableFuture() {
    log.info("使用 Spring 异步任务发送邮件,并且获取任务返回结果示例");
    TimeUnit.SECONDS.sleep(2l);
    return AsyncResult.forValue("success");
}

Controller 层代码如下所示:

ListenableFuture<String> listenableFuture = emailService.sendEmailAsyncWithListenableFuture();
// 异步回调处理
listenableFuture.addCallback(new SuccessCallback<String>() {
    @Override
    public void onSuccess(String result) {
        log.info("异步回调处理返回值");
    }
}, new FailureCallback() {
    @Override
    public void onFailure(Throwable ex) {
        log.error("异步回调处理异常",ex);
    }
});

看到这里,如果有同学有疑惑,我们返回对象是 AsyncResult,为什么方法返回类可以是 Future,又可以是 ListenableFuture?

看完这张类继承关系,大家应该就知道答案了。

96.jpg

相关文章
|
3天前
|
安全 Java 数据库连接
Spring Boot 优雅关机时异步线程安全优化
Spring Boot 优雅关机时异步线程安全优化
8 1
|
6天前
|
存储 NoSQL Java
教程:Spring Boot与RocksDB本地存储的整合方法
教程:Spring Boot与RocksDB本地存储的整合方法
|
7天前
|
NoSQL Java 机器人
教程:Spring Boot与ETCD键值存储的整合
教程:Spring Boot与ETCD键值存储的整合
|
1天前
|
Java 数据处理 数据库
Spring Boot中的批处理任务实现
Spring Boot中的批处理任务实现
|
3天前
|
Java 索引 Spring
教程:Spring Boot中集成Elasticsearch的步骤
教程:Spring Boot中集成Elasticsearch的步骤
|
3天前
|
Java API Spring
教程:Spring Boot中如何集成GraphQL
教程:Spring Boot中如何集成GraphQL
|
3天前
|
缓存 Java Spring
教程:Spring Boot中集成Memcached的详细步骤
教程:Spring Boot中集成Memcached的详细步骤
|
6天前
|
Java API UED
Spring Boot中如何处理异步任务
Spring Boot中如何处理异步任务
|
6天前
|
Java API Spring
Spring Boot中如何处理异步任务
Spring Boot中如何处理异步任务
|
6天前
|
搜索推荐 Java 机器人
教程:Spring Boot中集成Elasticsearch的步骤
教程:Spring Boot中集成Elasticsearch的步骤