<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          千萬(wàn)不要這樣使用@Async注解

          共 5228字,需瀏覽 11分鐘

           ·

          2021-10-28 21:59

          你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

          你來(lái),我們一起精進(jìn)!你不來(lái),我和你的競(jìng)爭(zhēng)對(duì)手一起精進(jìn)!

          編輯:業(yè)余草

          chuckfang.com/2019/11/13/Async

          推薦:https://www.xttblog.com/?p=5288

          在實(shí)際的項(xiàng)目中,對(duì)于一些用時(shí)比較長(zhǎng)的代碼片段或者函數(shù),我們可以采用異步的方式來(lái)執(zhí)行,這樣就不會(huì)影響整體的流程了。比如我在一個(gè)用戶(hù)請(qǐng)求中需要上傳一些文件,但是上傳文件的耗時(shí)會(huì)相對(duì)來(lái)說(shuō)比較長(zhǎng),這個(gè)時(shí)候如果上傳文件的成功與否不影響主流程的話(huà),就可以把上傳文件的操作異步化,在spring boot中比較常見(jiàn)的方式就是把要異步執(zhí)行的代碼片段封裝成一個(gè)函數(shù),然后在函數(shù)頭使用@Async注解,就可以實(shí)現(xiàn)代碼的異步執(zhí)行(當(dāng)然首先得在啟動(dòng)類(lèi)上加上@EnableAsync注解了)。

          具體的使用方式這里我也就不再演示了,網(wǎng)上教大家使用@Async的很多。今天我要講的并不是怎么去使用@Async注解,而是講我在實(shí)際開(kāi)發(fā)過(guò)程中遇到的一個(gè)坑,希望你不要再犯。

          首先,再明確一點(diǎn),學(xué)習(xí)一個(gè)知識(shí),第一步是找到相應(yīng)的官網(wǎng)或是比較權(quán)威的網(wǎng)站。

          那么這個(gè)坑是什么呢?就是如果你在同一個(gè)類(lèi)里面調(diào)用一個(gè)自己的被@Async修飾的函數(shù)時(shí),這個(gè)函數(shù)將不會(huì)被異步執(zhí)行,它依然是同步執(zhí)行的!所以你如果沒(méi)有經(jīng)過(guò)測(cè)試就想當(dāng)然的以為只要在方法頭加上@Async就能達(dá)到異步的效果,那么你很有可能會(huì)得到相反的效果。這個(gè)是很要命的。

          所以我來(lái)給你們演示一下,這個(gè)效果是多么恐怖。為什么說(shuō)它恐怖,是因?yàn)樵诔绦騿T的眼中,一切不符合期望的行為都是bug,bug能不恐怖嗎?

          首先我們先看一個(gè)正確使用的方式,建一個(gè)spring boot項(xiàng)目,如果你是用Intellij IDEA新建的項(xiàng)目,記得勾上web的依賴(lài)。

          項(xiàng)目建好后,我們?cè)趩?dòng)類(lèi)上加上@EnableAsync注解:

          import?org.springframework.boot.SpringApplication;
          import?org.springframework.boot.autoconfigure.SpringBootApplication;
          import?org.springframework.scheduling.annotation.EnableAsync;

          @SpringBootApplication
          @EnableAsync
          public?class?AsyncdemoApplication?{

          ????public?static?void?main(String[]?args)?{
          ????????SpringApplication.run(AsyncdemoApplication.class,?args);
          ????}

          }

          然后再新建一個(gè)類(lèi)Task,用來(lái)放三個(gè)異步任務(wù)doTaskOne、doTaskTwo、doTaskThree:

          import?org.springframework.scheduling.annotation.Async;
          import?org.springframework.stereotype.Component;

          import?java.util.Random;

          /**
          ?*?@author?https://www.chuckfang.top
          ?*?@date?Created?on?2019/11/12?11:34
          ?*/

          @Component
          public?class?Task?{

          ????public?static?Random?random?=?new?Random();

          ????@Async
          ????public?void?doTaskOne()?throws?Exception?{
          ????????System.out.println("開(kāi)始做任務(wù)一");
          ????????long?start?=?System.currentTimeMillis();
          ????????Thread.sleep(random.nextInt(10000));
          ????????long?end?=?System.currentTimeMillis();
          ????????System.out.println("完成任務(wù)一,耗時(shí):"?+?(end?-?start)?+?"毫秒");
          ????}

          ????@Async
          ????public?void?doTaskTwo()?throws?Exception?{
          ????????System.out.println("開(kāi)始做任務(wù)二");
          ????????long?start?=?System.currentTimeMillis();
          ????????Thread.sleep(random.nextInt(10000));
          ????????long?end?=?System.currentTimeMillis();
          ????????System.out.println("完成任務(wù)二,耗時(shí):"?+?(end?-?start)?+?"毫秒");
          ????}

          ????@Async
          ????public?void?doTaskThree()?throws?Exception?{
          ????????System.out.println("開(kāi)始做任務(wù)三");
          ????????long?start?=?System.currentTimeMillis();
          ????????Thread.sleep(random.nextInt(10000));
          ????????long?end?=?System.currentTimeMillis();
          ????????System.out.println("完成任務(wù)三,耗時(shí):"?+?(end?-?start)?+?"毫秒");
          ????}
          }

          在單元測(cè)試類(lèi)上注入Task,在測(cè)試用例上測(cè)試這三個(gè)方法的執(zhí)行過(guò)程:

          @SpringBootTest
          class?AsyncdemoApplicationTests?{

          ????public?static?Random?random?=?new?Random();

          ????@Autowired
          ????Task?task;

          ????@Test
          ????void?contextLoads()?throws?Exception?{
          ????????task.doTaskOne();
          ????????task.doTaskTwo();
          ????????task.doTaskThree();
          ????????Thread.sleep(10000);
          ????}
          }

          為了讓這三個(gè)方法執(zhí)行完,我們需要再單元測(cè)試用例上的最后一行加上一個(gè)延時(shí),不然等函數(shù)退出了,異步任務(wù)還沒(méi)執(zhí)行完。

          我們啟動(dòng)看看效果:

          ?

          開(kāi)始做任務(wù)三
          開(kāi)始做任務(wù)二
          開(kāi)始做任務(wù)一
          完成任務(wù)一,耗時(shí):4922毫秒
          完成任務(wù)三,耗時(shí):6778毫秒
          完成任務(wù)二,耗時(shí):6960毫秒

          ?

          我們看到三個(gè)任務(wù)確實(shí)是異步執(zhí)行的,那我們?cè)倏纯村e(cuò)誤的使用方法。

          我們?cè)跍y(cè)試類(lèi)里面把這三個(gè)函數(shù)再寫(xiě)一遍,并在測(cè)試用例上調(diào)用測(cè)試類(lèi)自己的方法:

          @SpringBootTest
          class?AsyncdemoApplicationTests?{

          ????public?static?Random?random?=?new?Random();

          ????@Test
          ????void?contextLoads()?throws?Exception?{
          ????????doTaskOne();
          ????????doTaskTwo();
          ????????doTaskThree();
          ????????Thread.sleep(10000);
          ????}

          ????@Async
          ????public?void?doTaskOne()?throws?Exception?{
          ????????System.out.println("開(kāi)始做任務(wù)一");
          ????????long?start?=?System.currentTimeMillis();
          ????????Thread.sleep(random.nextInt(10000));
          ????????long?end?=?System.currentTimeMillis();
          ????????System.out.println("完成任務(wù)一,耗時(shí):"?+?(end?-?start)?+?"毫秒");
          ????}

          ????@Async
          ????public?void?doTaskTwo()?throws?Exception?{
          ????????System.out.println("開(kāi)始做任務(wù)二");
          ????????long?start?=?System.currentTimeMillis();
          ????????Thread.sleep(random.nextInt(10000));
          ????????long?end?=?System.currentTimeMillis();
          ????????System.out.println("完成任務(wù)二,耗時(shí):"?+?(end?-?start)?+?"毫秒");
          ????}

          ????@Async
          ????public?void?doTaskThree()?throws?Exception?{
          ????????System.out.println("開(kāi)始做任務(wù)三");
          ????????long?start?=?System.currentTimeMillis();
          ????????Thread.sleep(random.nextInt(10000));
          ????????long?end?=?System.currentTimeMillis();
          ????????System.out.println("完成任務(wù)三,耗時(shí):"?+?(end?-?start)?+?"毫秒");
          ????}
          }

          我們?cè)倏纯葱Ч?/p>

          ?

          開(kāi)始做任務(wù)一
          完成任務(wù)一,耗時(shí):9284毫秒
          開(kāi)始做任務(wù)二
          完成任務(wù)二,耗時(shí):8783毫秒
          開(kāi)始做任務(wù)三
          完成任務(wù)三,耗時(shí):943毫秒

          ?

          它們竟然是順序執(zhí)行的!也就是同步執(zhí)行,并沒(méi)有達(dá)到異步的效果,這要是在生產(chǎn)上使用,豈不涼涼。

          這種問(wèn)題如果不進(jìn)行測(cè)試還是比較難發(fā)現(xiàn)的,特別是你想要異步執(zhí)行的代碼并不會(huì)執(zhí)行太久,也就是同步執(zhí)行你也察覺(jué)不出來(lái),或者說(shuō)你根本發(fā)現(xiàn)不了它是不是異步執(zhí)行。這種錯(cuò)誤也很容易犯,特別是當(dāng)你把一個(gè)類(lèi)里面的方法提出來(lái)想要異步執(zhí)行的時(shí)候,你并不會(huì)想著新建一個(gè)類(lèi)來(lái)放這個(gè)方法,而是會(huì)在當(dāng)前類(lèi)上直接抽取為一個(gè)方法,然后在方法頭上加上@Async注解,你以為這樣就完事了,其實(shí)并沒(méi)有起到異步的作用!我也是在改進(jìn)我們項(xiàng)目的文件上傳時(shí)才發(fā)現(xiàn)這個(gè)問(wèn)題的。因?yàn)槲募蟼饕膊粫?huì)花費(fèi)太久,所以真的很隱蔽。

          其實(shí)@Async的這個(gè)性質(zhì)在官網(wǎng)上已經(jīng)有過(guò)說(shuō)明了,官網(wǎng):https://www.baeldung.com/spring-async是這樣說(shuō)的:

          ?

          First – let’s go over the rules – @Async has two limitations:

          • it must be applied to public methods only
          • self-invocation – calling the async method from within the same class – won’t work

          The reasons are simple – 「the method needs to be *public*」 so that it can be proxied. And 「self-invocation doesn’t work」 because it bypasses the proxy and calls the underlying method directly.

          ?

          文章在一開(kāi)始就提到了@Async的兩個(gè)限制,其中第二個(gè)就是調(diào)用自己類(lèi)上的異步方法是不起作用的。下面也講了原因,就是這種使用方式繞過(guò)了代理而直接調(diào)用了方法,所以肯定是同步的了。從這里,我們也知道了另外一個(gè)知識(shí)點(diǎn),就是@Async注解其實(shí)是通過(guò)代理的方式來(lái)實(shí)現(xiàn)異步調(diào)用的。

          上面這個(gè)錯(cuò)誤使用方法,我目前沒(méi)有在網(wǎng)上看到過(guò)有人說(shuō)明,甚至在程序員DD的博客中也沒(méi)有對(duì)此進(jìn)行說(shuō)明,我深表遺憾。希望你看完我的博客之后不要再犯同樣的錯(cuò)誤了,或者你趕快檢查一下你自己的項(xiàng)目中有沒(méi)有這樣使用@Async注解的。如果覺(jué)得文章不錯(cuò),可以推薦給同事看哦,提醒他們正確使用@Async。

          瀏覽 51
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美黄色电影一区二区在线播放 | 成人操碰视频 | 日韩中文字幕组 | 成人V| 精品国视频 |