1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        千萬不要這樣使用@Async注解

        共 5228字,需瀏覽 11分鐘

         ·

        2021-10-28 21:59

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

        你來,我們一起精進!你不來,我和你的競爭對手一起精進!

        編輯:業(yè)余草

        chuckfang.com/2019/11/13/Async

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

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

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

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

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

        所以我來給你們演示一下,這個效果是多么恐怖。為什么說它恐怖,是因為在程序員的眼中,一切不符合期望的行為都是bug,bug能不恐怖嗎?

        首先我們先看一個正確使用的方式,建一個spring boot項目,如果你是用Intellij IDEA新建的項目,記得勾上web的依賴。

        項目建好后,我們在啟動類上加上@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);
        ????}

        }

        然后再新建一個類Task,用來放三個異步任務(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("開始做任務(wù)一");
        ????????long?start?=?System.currentTimeMillis();
        ????????Thread.sleep(random.nextInt(10000));
        ????????long?end?=?System.currentTimeMillis();
        ????????System.out.println("完成任務(wù)一,耗時:"?+?(end?-?start)?+?"毫秒");
        ????}

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

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

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

        @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);
        ????}
        }

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

        我們啟動看看效果:

        ?

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

        ?

        我們看到三個任務(wù)確實是異步執(zhí)行的,那我們再看看錯誤的使用方法。

        我們在測試類里面把這三個函數(shù)再寫一遍,并在測試用例上調(diào)用測試類自己的方法:

        @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("開始做任務(wù)一");
        ????????long?start?=?System.currentTimeMillis();
        ????????Thread.sleep(random.nextInt(10000));
        ????????long?end?=?System.currentTimeMillis();
        ????????System.out.println("完成任務(wù)一,耗時:"?+?(end?-?start)?+?"毫秒");
        ????}

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

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

        我們再看看效果:

        ?

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

        ?

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

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

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

        ?

        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.

        ?

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

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

        瀏覽 51
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            人妻无码视频 | 乱亲h女秽乱长久久久 | 浪妇荡sao嗯啊~h小雯 | 中文字幕永久在线观看 | 男女床上动作片 | 99久久久无码国产成人 | 青青草中文字幕在线 | 在线主播精品国产 | 狼友视频首页 | 黄频视频在线观看免费 |