不仅会用@Async,我把源码也梳理了一遍(上)

说起异步化,很多人会想起异步线程、消息队列等,消息队列不是文章的主题,今天我们来聊聊spring对异步化的支持@EnableAsync&@Async。

我会分为以下几个步骤去说明,首先说说用法,然后再从源码层分析@Async的底层原理,同学们准备好了吗?

@Async用法

想要开启异步化,我们就必须要用用到@EnableAsync注解,这又是我们之前给大家说个的@EnableXXX的模块,大家可以回顾一下:《导图梳理springboot手动、自动装配,让springboot不再难懂》,配置类中开启了@EnableAsync之后,@Async的注解就开始起作用了,我们先来做个简单的测试:

配置类

  • com.example.demo.config.AsyncConfig
@EnableAsync
@Configuration
public class AsyncConfig {

}

一个页面控制器

  • com.example.demo.controller.AsyncController
@Slf4j
@RestController
public class AsyncController {

    @Autowired
    UserService userService;

    @GetMapping("/tt")
    public Object tt() throws InterruptedException {

        String username = userService.findUserNameById(1L);
        log.info("AsyncController----------->" + Thread.currentThread().getName());
        log.info("username---------------->" + username);
        return username;
    }
}

然后就是业务类

  • com.example.demo.service.impl.UserServiceImpl
@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Async
    public String findUserNameById(Long id) {

        log.info("UserServiceImpl----------->" + Thread.currentThread().getName());
        return "关注公众号:java思维导图";
    }
}

好了,最简单的用法已经完成了,我在UserServiceImpl#findUserNameById方法上添加了@Async注解,表示此方法是个异步方法。我们先来看看执行结果,然后再来说说上面的程序有什么问题。

控制台输出:

可以看出,两个方法执行的线程是不一样的,明显UserServiceImpl的findUserNameById方法是另起了一个线程task-1执行。

这里我们注意到了一个问题,controller中我们获取不到了异步方法的返回值username了。所以想要获取@Async注解之后的异步方法就不能使用这种方式了,所以我们改下程序:

service中新添加个方法:

@Async
public Future<String> findLastNameById(Long id) {

    log.info("UserServiceImpl----------->" + Thread.currentThread().getName());

    return new AsyncResult<String>("关注公众号:java思维导图");
}

然后controller改成这样:

@GetMapping("/tt")
public Object tt() throws InterruptedException, ExecutionException {

    String username = userService.findUserNameById(1L);
    log.info("AsyncController----------->" + Thread.currentThread().getName());
    log.info("username---------------->" + username);


    Future<String> future = userService.findLastNameById(1L);
    String lastname = future.get();
    log.info("lastname---------------->" + lastname + Thread.currentThread().getName());

    return username;
}

测试结果如下:

可以看到task-1获取不到结果,task-2获取到结果了,但是这里有个问题,为了获取异步的结果,线程http-nio-8080-exec-1一直在等待future.get()的返回结果,才继续往下走,这就有点影响主线程的性能了。

那如果异步方法出现了异常,我们怎么去捕捉呢,一般全局异常处理我们都是处理controller中的异常,但这个异步线程的异常能不能捕捉到都是个问题。其实,对于@Async的异常,是可以配置的。AsyncConfig中我们可以继承一个AsyncConfigurer,然后重写两个方法:

  • com.example.demo.config.AsyncConfig
@EnableAsync
@Configuration
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {

        //线程池
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(5);
        taskExecutor.setMaxPoolSize(10);
        taskExecutor.setQueueCapacity(25);
        taskExecutor.initialize();
        return taskExecutor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}

一个定义线程池,一个定义异常处理器,线程池我们就不说了,这个默认的SimpleAsyncUncaughtExceptionHandler没啥东西:

public class SimpleAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

   private static final Log logger = LogFactory.getLog(SimpleAsyncUncaughtExceptionHandler.class);


   @Override
   public void handleUncaughtException(Throwable ex, Method method, Object... params) {
      if (logger.isErrorEnabled()) {
         logger.error("Unexpected exception occurred invoking async method: " + method, ex);
      }
   }

}

所以如果我们想自己处理这个出现异常之后的逻辑,还得重写AsyncUncaughtExceptionHandler 的handleUncaughtException接口,里面有method、params等参数,可以进行重试、或者其他处理等。

异步化原理

好吧,下一篇再写了,今天先到这里,下午还有点事~

待续~

结束语

坚持原创的第三篇(20190920),打卡打卡。希望你们会喜欢。

我是吕一明,欢迎关注我的公众号:java思维导图

做个小调查:

留言区留言:你们公司是用什么方式实现异步处理业务的?

本文分享自微信公众号 - java思维导图(java-mindmap)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-09-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大道七哥

logstash使用template提前设置好maping同步mysql数据到Elasticsearch5.5.2

上篇blog说到采用logstash-input-jdbc将mysql数据同步到ES(http://www.cnblogs.com/jstarseven/p/7...

25320
来自专栏大道七哥

tampermonkey,采用js解析自定义脚本,实现网页列表数据采集分析

最近一直在做数据采集的事情,目的是使用java开发一套分析指定采集规则,模拟用户动作做数据提取。 因此定义了一套动作脚本,open,click,get,list...

10820
来自专栏陶士涵的菜地

[javascript] 看知乎学习js事件触发过程

调用元素对象的addEventListener()方法,参数:事件,回调函数,是否捕获(true代表捕获阶段,false代表冒泡阶段,ie浏览器不支持在捕获阶段...

12210
来自专栏陶士涵的菜地

[javascript] 看知乎学习js闭包

词法作用域,就是,按照代码书写时的样子,内部函数可以访问函数外面的变量。引擎通过数据结构和算法表示一个函数,使得在代码解释执行时按照词法作用域的规则,可以访问外...

8510
来自专栏用户1175783的专栏

# Event loop

​ dart是一种单线程语言,异步模型主要是通过事件轮询(event loop)来实现,另外也提供了更高级的Isolate来支持多线程,通常用于计算比较耗时的操...

14930
来自专栏未闻Code

新闻类网页正文通用抽取器(一)——项目介绍

开发这个项目,源自于我在知网发现了一篇关于自动化抽取新闻类网站正文的算法论文——《基于文本及符号密度的网页正文提取方法》

16320
来自专栏陶士涵的菜地

[android] WebView与Js交互

调用WebView对象的getSettings()方法,获取WebSettings对象

10330
来自专栏浪子编程走四方

Composer 使用使用详解

Composer 是 PHP 的一个包依赖管理工具。我们可以在项目中声明所依赖的外部工具库,Composer 会帮你安装这些依赖的库文件,有了它,我们就可以很轻...

13930
来自专栏蚂蚁开源社区

简洁的HTML5视频播放器UI特效开源库

近期在做一个项目时,遇到了播放视频的问题,找了很多插件和方法,今天介绍的这个是比较认为简洁和功能强大的开源库,可以任意修改播放格式。

16030
来自专栏前端达人

JavaScript Fetch API 新手入门指南

文章来源:https://www.oxxostudio.tw/articles/201908/js-fetch.html

17110

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励