自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一文搞懂 @Async 注解原理

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

一、先寫個(gè)Demo

我們直接使用 SpringBoot 搭建個(gè) Demo,首先就是啟動(dòng)類,加入 @EnableAsync 注解。

這就是個(gè)別同學(xué)使用 @Async 注解不生效的原因,沒有在啟動(dòng)類中打開異步的開關(guān)。

再寫一個(gè)Service,定義一個(gè)異步方法async。

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

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

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

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

啟動(dòng),調(diào)用一下看下輸出。

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

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

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

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

二、@Async 注解原理

第一步,找到 @Async注解。

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

  • 首先我會(huì)看注釋,尤其是注釋中可以點(diǎn)進(jìn)去的類,比如 See Also中的我會(huì)重點(diǎn)關(guān)注。
  • 其次是看注解的參數(shù)調(diào)用的地方。

通過上面兩步,發(fā)現(xiàn) value 參數(shù)調(diào)用的類正好與上面標(biāo)注的 AnnotationAsyncExecutionInterceptor 吻合,所以直接跳到代碼調(diào)用的位置。

其實(shí)在這里我們就可以直接斷點(diǎn),然后根據(jù)調(diào)用棧就知道在哪調(diào)用的了。

打個(gè)斷點(diǎn),啟動(dòng)程序,http 調(diào)用異步方法 async。

需要注意,如果你斷點(diǎn)進(jìn)不來,那就重啟,在應(yīng)用程序啟動(dòng)之后的第一次訪問中會(huì)被攔住。(原因后面說,在第五節(jié)自定義線程池)

點(diǎn)擊圖片中紅框起來的位置,發(fā)現(xiàn)跳到的代碼位置正好與 See Also中標(biāo)注的是同一個(gè)方法AsyncExecutionAspectSupport#determineAsyncExecutor ,說明我們沒有找錯(cuò)地方,那么我們就開始在這個(gè)位置,再加入一個(gè)斷點(diǎn),開始我們的 debug 。

AsyncExecutionAspectSupport#determineAsyncExecutor 方法中,其實(shí)就是 value 參數(shù)生效的地方。

  • 80 行代碼處,獲取注解 @Async 的 value 值。
  • 83 行代碼處,如果有設(shè)置的 value 值,去 spring 容器中獲取對(duì)應(yīng)的執(zhí)行器,對(duì)于我們這就是獲取對(duì)應(yīng)的線程池。
  • 85 行代碼處,如果沒有設(shè)置 value 值,就返回默認(rèn)的 defaultExecutor 。

繼續(xù)斷點(diǎn)處往下走,所以 AsyncExecutionAspectSupport#determineAsyncExecutor 方法就是返回執(zhí)行任務(wù)的線程池。

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

為什么封裝為一個(gè) Callable 可以評(píng)論區(qū)聊一下,看看八股文忘了沒有。

繼續(xù)往下,就到了代碼 56 行的位置,提交給線程池執(zhí)行任務(wù)。

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

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

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

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

在 AsyncExecutionInterceptor 類中,重寫了父類的 getDefaultExecutor ,當(dāng)我們沒有指定 value 參數(shù)時(shí),就會(huì)走到該方法,返回一個(gè)applicationTaskExecutor的線程池。

細(xì)心的同學(xué)應(yīng)該看到了吧,此處線程的前綴就是task-,這不就對(duì)應(yīng)我們文章開頭日志輸出的線程名稱了嗎。

四、線程池的配置

那么這個(gè)線程池是在哪里初始化的呢?我們也沒有看到初始化的代碼?。?/p>

下面我分享一個(gè)找配置的方法,現(xiàn)在我們 beanName 已經(jīng)知道了,直接全局搜索一下不就好了。

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

代碼中就一行,執(zhí)行的 build ,所以我們直接進(jìn)入。

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

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

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

五、可以自定義線程池嗎

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

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

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

需要注意的就兩點(diǎn):

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

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

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

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

上面是首次調(diào)用的時(shí)候的邏輯,注意看上圖的 78 行,所以當(dāng)你第二次調(diào)用的時(shí)候就不會(huì)走這個(gè)邏輯了。

那么這個(gè) executors 是什么呢?點(diǎn)進(jìn)去看一下。

在此處打個(gè)斷點(diǎn)看一下,executors 其實(shí)就是一個(gè) map,key 就是注解標(biāo)注的方法,value 就是該方法對(duì)應(yīng)的線程池。

這就是為什么上文中只有程序第一次啟動(dòng)的時(shí)候才會(huì)進(jìn)入到獲取注解屬性值的方法。

六、返回值怎么獲取

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

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

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

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

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

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

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

而在 doSubmit 方法中,校驗(yàn)方法返回值類型是不是Future類型,如果不是直接提交任務(wù),返回 null。

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

總結(jié)

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

大致流程如下:

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

在第5步中,springboot 中如果獲取 taskExecutor 實(shí)例時(shí),因?yàn)橐肓说谌降膉ar,獲取到了第三方的線程池,可能會(huì)遇到意想不到的 bug,這個(gè)點(diǎn)是需要注意的。

責(zé)任編輯:趙寧寧 來源: 醉魚Java
相關(guān)推薦

2023-09-08 08:20:46

ThreadLoca多線程工具

2021-01-13 05:21:59

參數(shù)

2021-07-08 10:08:03

DvaJS前端Dva

2023-09-22 10:45:47

云原生云計(jì)算

2023-12-15 15:55:24

Linux線程同步

2024-04-12 12:19:08

語言模型AI

2022-03-24 08:51:48

Redis互聯(lián)網(wǎng)NoSQL

2021-04-27 19:21:48

HBase原理開源

2021-03-22 10:05:59

netstat命令Linux

2023-09-15 12:00:01

API應(yīng)用程序接口

2020-04-15 16:30:24

掃碼登錄微信前端

2019-04-03 09:27:01

MySQLInnoDB務(wù)ACID

2019-11-19 08:00:00

神經(jīng)網(wǎng)絡(luò)AI人工智能

2020-03-18 14:00:47

MySQL分區(qū)數(shù)據(jù)庫

2021-06-30 08:45:02

內(nèi)存管理面試

2022-06-07 10:13:22

前端沙箱對(duì)象

2022-08-15 15:39:23

JavaScript面向?qū)ο?/a>數(shù)據(jù)

2023-04-03 15:04:00

RPCPHP語言

2023-08-24 16:50:45

2023-10-16 08:16:31

Bean接口類型
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)