成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

一文搞懂 @Async 注解原理

開發
一個注解就搞定異步開發,太爽了,不知道大家都在項目中用過 @Async 注解嗎?今天,我們就來聊聊。

一、先寫個Demo

我們直接使用 SpringBoot 搭建個 Demo,首先就是啟動類,加入 @EnableAsync 注解。

這就是個別同學使用 @Async 注解不生效的原因,沒有在啟動類中打開異步的開關。

再寫一個Service,定義一個異步方法async。

@Service
public class TestService {
    public final Logger log = LoggerFactory.getLogger(getClass());
    @Async
    public void async(){
        log.info("異步線程消息輸出:{}",Thread.currentThread().getName());
    }
}

注意:異步方法所在的類需要被 spring 管理。

定義調用類,上面說了,需要被 spring 管理,所以調用的時候也需要使用注入的方式進行調用,如果使用 new 或者本類方法調用都是不能生效的。

@RestController
@RequestMapping("/test")
public class TestController {
    @Autowired
    private TestService testService;
    @GetMapping("/async")
    public Object async() throws Exception {
        testService.async();
        return "success";
    }
}

啟動,調用一下看下輸出。

[task-1]c.z.e.encry.service.TestService:異步線程消息輸出:task-1

可以看到,打印該日志的線程已經是另一個線程,且線程名task-1。那么是不是可以猜測一下使用的線程池中線程名稱前綴為task-,這里先提一嘴,后面我們來揭秘。

到了這,我們的 demo 就搭建完成了,下面開始吃源碼吧,源碼之下無秘密,Debug 啟動。

先看一下 @Async 工作流程圖幫助理解。

二、@Async 注解原理

第一步,找到 @Async注解。

注解的內部很簡單,就一個 value 屬性,這個屬性我們后面再說,我先分享一下平常我是如何看源碼的。

  • 首先我會看注釋,尤其是注釋中可以點進去的類,比如 See Also中的我會重點關注。
  • 其次是看注解的參數調用的地方。

通過上面兩步,發現 value 參數調用的類正好與上面標注的 AnnotationAsyncExecutionInterceptor 吻合,所以直接跳到代碼調用的位置。

其實在這里我們就可以直接斷點,然后根據調用棧就知道在哪調用的了。

打個斷點,啟動程序,http 調用異步方法 async。

需要注意,如果你斷點進不來,那就重啟,在應用程序啟動之后的第一次訪問中會被攔住。(原因后面說,在第五節自定義線程池)

點擊圖片中紅框起來的位置,發現跳到的代碼位置正好與 See Also中標注的是同一個方法AsyncExecutionAspectSupport#determineAsyncExecutor ,說明我們沒有找錯地方,那么我們就開始在這個位置,再加入一個斷點,開始我們的 debug 。

AsyncExecutionAspectSupport#determineAsyncExecutor 方法中,其實就是 value 參數生效的地方。

  • 80 行代碼處,獲取注解 @Async 的 value 值。
  • 83 行代碼處,如果有設置的 value 值,去 spring 容器中獲取對應的執行器,對于我們這就是獲取對應的線程池。
  • 85 行代碼處,如果沒有設置 value 值,就返回默認的 defaultExecutor 。

繼續斷點處往下走,所以 AsyncExecutionAspectSupport#determineAsyncExecutor 方法就是返回執行任務的線程池。

  • 如果為空拋出異常(代碼40行)。
  • 否則就封裝我們的異步方法 async 為 Callable。

為什么封裝為一個 Callable 可以評論區聊一下,看看八股文忘了沒有。

繼續往下,就到了代碼 56 行的位置,提交給線程池執行任務。

所以到了這,你看明白了嗎?其實 @Async 注解的核心代碼就是 AsyncExecutionInterceptor#invoke() 方法,只要這個方法主線找到了,邏輯通了,那么@ Async 注解原理還不是手到擒來。

三、底層是不是使用的線程池

回到這個問題上來,底層是不是使用的線程池相信你已經有了答案了吧,在 springboot 中,起碼是使用的線程池。

當 value 屬性值為空時,spring會使用 SimpleAsyncTaskExecutor 執行任務,而該類都是通過 new Thread() 執行任務的,具體可查看 SimpleAsyncTaskExecutor#doExecute(Runnable task) 。

在 AsyncExecutionInterceptor 類中,重寫了父類的 getDefaultExecutor ,當我們沒有指定 value 參數時,就會走到該方法,返回一個applicationTaskExecutor的線程池。

細心的同學應該看到了吧,此處線程的前綴就是task-,這不就對應我們文章開頭日志輸出的線程名稱了嗎。

四、線程池的配置

那么這個線程池是在哪里初始化的呢?我們也沒有看到初始化的代碼啊?

下面我分享一個找配置的方法,現在我們 beanName 已經知道了,直接全局搜索一下不就好了。

這個還算比較順利,全局一查找,就一個,不就是你嗎。代碼位置(TaskExecutionAutoConfiguration#applicationTaskExecutor)

代碼中就一行,執行的 build ,所以我們直接進入。

先看一下 configure,簡簡單單的一波 set 的操作。

所以知道線程池的配置在哪了嗎,那肯定就是 new ThreadPoolTaskExecutor 這了。

在 configure 處打一個斷點,即可看到全部的配置信息,包括線程名稱前綴的指定都在這了。

五、可以自定義線程池嗎

那么可以自定義線程池嗎,當然可以。還記得文章開頭我們提到的 @Async 注解中的 value 屬性嗎,它的值就是指定線程池名稱的。

我們通過一個代碼示例來看下是如何使用自定義線程池的。

上文中我們自定義的線程池,把線程名的前綴改為了zuiyuThreadPool-,如果生效,日志將會打印出線程名稱。

需要注意的就兩點:

  • @Bean 中自定義線程池的注冊bean名稱
  • @Async中指定線程池的名稱,保證與第一步的名稱保持一致。

啟動程序,debug 開始。還記得剛開始我們查找 @Async 注解中value參數使用的地方嗎?這個地方就是獲取我們注解中值的位置。

在 determineAsyncExecutor 方法處,第 80 行 this.getExecutorQualifier(method) 就是獲取注解中值的代碼,此處返回了zuiyuThreadPool。實現是 AnnotationAsyncExecutionInterceptor#getExecutorQualifier。

然后 this.findQualifiedExecutor(this.beanFactory, qualifier) 這一行代碼中,只做了一件事,就是拿著 value 值去 beanFactory 中查找對應的 bean 對象返回。

上面是首次調用的時候的邏輯,注意看上圖的 78 行,所以當你第二次調用的時候就不會走這個邏輯了。

那么這個 executors 是什么呢?點進去看一下。

在此處打個斷點看一下,executors 其實就是一個 map,key 就是注解標注的方法,value 就是該方法對應的線程池。

這就是為什么上文中只有程序第一次啟動的時候才會進入到獲取注解屬性值的方法。

六、返回值怎么獲取

如果想獲取接口的返回值有什么方法嗎?

在@Async 注釋的地方,返回類型只能是 void 或者java.util.concurrent.Future。

所以我們只需要把異步的方法,改為這兩種形式的返回值即可。

假如我們想返回 String 類型的值,可以這樣做。

如上圖,我們把異步方法改為 CompletableFuture<String> 的形式就可以返回 String 類型的值了,使用Funture.get() 方法就可以讀取到該值。

其實歸根結底還是線程池,你還記得 AsyncExecutionInterceptor#invoke 方法嗎,最后一行代碼不就是 submit 提交任務。

我們把異步的方法封裝為了一個 Callable task,然后提交。

而在 doSubmit 方法中,校驗方法返回值類型是不是Future類型,如果不是直接提交任務,返回 null。

所以,知道為什么在異步方法中需要封裝為 Future 了吧,如果不封裝為Future類型,返回為 null,是獲取不到結果的。

總結

@Async 注解的工作原理就是文章開頭給出流程圖所示的流程。核心代碼就是AsyncExecutionInterceptor#invoke() ,重點關注 determineAsyncExecutor() 與doSubmit()即可。

大致流程如下:

  1. 從緩存 map 中獲取線程池實例。
  2. 如果緩存中存在,直接返回。
  3. 如果緩存中不存在,判斷是否指定 value值。
  4. 如果指定 value 值,就去 beanFactory 中獲取對應的線程池實例。
  5. 如果沒有指定,value 為空,就獲取 taskExecutor的實例。
  6. 返回線程池實例。

在第5步中,springboot 中如果獲取 taskExecutor 實例時,因為引入了第三方的jar,獲取到了第三方的線程池,可能會遇到意想不到的 bug,這個點是需要注意的。

責任編輯:趙寧寧 來源: 醉魚Java
相關推薦

2023-09-08 08:20:46

ThreadLoca多線程工具

2021-07-08 10:08:03

DvaJS前端Dva

2021-01-13 05:21:59

參數

2023-12-15 15:55:24

Linux線程同步

2023-09-22 10:45:47

云原生云計算

2022-03-24 08:51:48

Redis互聯網NoSQL

2024-04-12 12:19:08

語言模型AI

2021-04-27 19:21:48

HBase原理開源

2021-03-22 10:05:59

netstat命令Linux

2023-09-15 12:00:01

API應用程序接口

2019-04-03 09:27:01

MySQLInnoDB務ACID

2023-09-02 21:27:09

2021-03-04 00:09:31

MySQL體系架構

2020-09-03 06:35:44

Linux權限文件

2023-05-22 13:27:17

2021-02-28 20:53:37

Cookie存儲瀏覽器

2023-03-06 21:29:41

mmap技術操作系統

2020-12-07 06:19:50

監控前端用戶

2022-07-15 08:16:56

Stream函數式編程

2020-04-15 16:30:24

掃碼登錄微信前端
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 成人免费视频在线观看 | 日韩一区二区三区在线 | 日韩中文字幕 | 国产精品久久一区二区三区 | 国产成人免费视频网站视频社区 | 久久久国产精品视频 | 理论片午午伦夜理片影院 | 日日干夜夜操 | 久久久久久色 | 久久久久久国产精品 | 亚洲伊人精品酒店 | 羞羞视频免费观看 | 成人影院午夜 | 999精品网 | 精品久久久久久久久久久久久 | www.中文字幕 | 日韩欧美在线免费观看 | 精品欧美一区二区三区免费观看 | 欧美精品久久久 | h视频在线播放 | 国产精品久久久久久 | 日韩淫片免费看 | 国产清纯白嫩初高生在线播放视频 | 欧美成人免费在线视频 | 精品国产青草久久久久福利 | 精品入口麻豆88视频 | 日韩精品视频在线 | 亚洲欧美日韩中文在线 | 天堂视频一区 | 亚洲成人免费视频 | 国产精品精品视频一区二区三区 | 国产精品久久久久aaaa九色 | 精品国产乱码一区二区三 | 国产在线视频三区 | 蜜桃特黄a∨片免费观看 | 欧美成人免费在线视频 | 在线视频a | 欧美精品一区二区在线观看 | 国产 日韩 欧美 在线 | 国产日韩欧美一区二区 | 精品国产欧美日韩不卡在线观看 |