Java aszinkron feldolgozás
Mielőtt megvizsgálnánk a Spring @Async-t, elengedhetetlen az szinkron, aszinkron és több szálú fogalmak megértése. Feltételezzük, hogy ismeri ezeket a fogalmakat, és nézzük meg a tiszta Java aszinkron feldolgozási módját kóddal. Ha már jártas a Java szálkezelésében, akkor ezt a fejezetet kihagyhatja.
Ha egy üzenetet fogadunk és egyszerűen kiírunk, akkor a kódot a fenti módon írhatjuk meg szinkron módon. Ha ezt több szálú aszinkron módra szeretnénk átírni, akkor a következőképpen írhatjuk meg a forráskódot.
Ez a módszer azonban nagyon veszélyes, mert nem tudjuk kezelni a szálakat. Például, ha 10 000 hívás érkezik egyszerre, akkor nagyon rövid idő alatt 10 000 szálat kell létrehozni. A szálak létrehozásának költsége nem elhanyagolható, ezért negatívan befolyásolja a program teljesítményét, és akár OOM hibát is okozhat. Ezért a szálak kezeléséhez implementálni kell a szálkészletet (Thread Pool), és Java az ExecutorService osztályt biztosítja erre a célra.
A teljes szál számát 10-re korlátozzuk, és helyesen tudjuk végrehajtani a kívánt több szálú aszinkron feldolgozást. Ez a módszer azonban minden egyes aszinkron feldolgozásra szánt metódushoz megköveteli az ExecutorService submit() metódusának alkalmazását, ami ismétlődő módosítási feladatokhoz vezet. Vagyis ha egy kezdetben szinkron logikával írt metódust aszinkronná szeretnénk alakítani, akkor elkerülhetetlenül módosítani kell a metódus logikáját magát.
Spring @Async
Egyszerű módszer
Az @EnableAsync annotációt az Application osztály fölé kell helyezni, és az aszinkron módon feldolgozandó szinkron logika metódusára az @Async annotációt kell helyezni, és ennyi. Ez a módszer azonban nem kezeli a szálakat. Miért? Mert az @Async alapértelmezett beállítása a SimpleAsyncTaskExecutor használata, amely nem szálkészlet, hanem egyszerűen csak szálakat hoz létre.
Szálkészlet használata
Először távolítsuk el az @EnableAsync-t az Application osztályból. Ha az Application osztályban be van állítva az @EnableAutoConfiguration vagy az @SpringBootApplication, akkor futásidőben beolvassa a SpringAsyncConfig osztály (amit alább fogunk létrehozni) threadPoolTaskExecutor bean információit, mert az @Configuration van rá beállítva.
Beállíthatjuk a core és max méretet. Ebben az esetben várhatóan a core méretű szálak fognak működni, és ha több feladatot kell kezelniük, akkor a max méretű szálakig fognak növekedni, de ez nem így van.
Belsőleg egy Integer.MAX_VALUE méretű LinkedBlockingQueue-et hoz létre, és ha a core méretű szálak nem tudják kezelni a feladatokat, akkor a Queue-ban várnak. Ha a Queue megtelik, akkor a max méretű szálakig növeli a szálak számát a feladatok feldolgozására.
Ha nem szeretnénk az Integer.MAX_VALUE-t beállítani a Queue méretére, akkor beállíthatjuk a queueCapacity-t. A fenti beállítás esetén először 3 szál fogja kezelni a feladatokat, és ha lelassul a feldolgozás, akkor a feladatokat egy 100 méretű Queue-ba helyezi, és ha további kérések érkeznek, akkor legfeljebb 30 szálat hoz létre a feladatok feldolgozására.
A szálkészlet beállítása után az @Async annotációval ellátott metódushoz hozzá kell adni a bean nevét.
Ha több különböző típusú szálkészletet szeretnénk beállítani, akkor több bean létrehozó metódust hozhatunk létre (pl. threadPoolTaskExecutor()), és az @Async beállításakor a kívánt szálkészlet bean-t adhatjuk meg.
Visszatérési típus szerinti visszaadott formátum
Nincs visszatérési érték
Az aszinkron feldolgozásra szánt metódusnak nincs szüksége az eredmény átadására. Ebben az esetben az @Async annotáció visszatérési típusát void-ra kell állítani.
Van visszatérési érték
Future, ListenableFuture, CompletableFuture típusokat használhatunk visszatérési típusként. Az aszinkron metódus visszatérési értékét new AsyncResult() -ba kell csomagolni.
[Future]
A future.get() blokkoló módon várja az eredmény visszaadását, amíg meg nem érkezik a kérés eredménye. Emiatt blokkoló aszinkron módszer lesz, ami nem túl jó teljesítményű. Általában nem használják a Future-t.
[ListenableFuture]
A ListenableFuture lehetővé teszi a feladatok nem blokkoló módon történő kezelését visszahívások (callback) segítségével. Az addCallback() metódus első paramétere a feladat befejezésének visszahívási metódusa, a második paramétere pedig a feladat hibás befejezésének visszahívási metódusa. Megjegyzés: mivel a szálkészlet core száljainak számát 3-ra állítottuk be, láthatjuk, hogy a „Task Start” üzenet először háromszor jelenik meg.
[CompletableFuture]
A ListenableFuture önmagában is alkalmas nem blokkoló logika implementálására, de ha a visszahíváson belül további visszahívásokra van szükség, akkor nagyon összetett kódot eredményez, amit „visszahívási pokolnak” (callback hell) neveznek.
<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F9f152db8-c015-43cd-bf13-85c594f5f218%2FUntitled.png?table=block&id=268ac0bc-ca7b-4bcb-b11b-ac611a5038b2&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/1455;" width="2000" height="1455"></span>
Természetesen ebben a leckében nem fogjuk részletesen tárgyalni a CompletableFuture-t, ezért ha szeretnéd megtudni, hogyan lehet kezelni az összetett visszahívásokat, nézd meg a lenti forráslinkeket.
A ListenableFuture visszahívási definíciójához képest javult az olvashatóság, és tökéletesen végrehajtja a nem blokkoló funkciókat. Ezért, ha az @Async használatakor van visszatérési érték, akkor javasolt a CompletableFuture használata.
Az @Async előnyei
A fejlesztők szinkron módon írhatják a metódusokat, és ha aszinkronná akarják tenni, akkor egyszerűen csak az @Async annotációt kell a metódus fölé helyezniük. Így jó karbantarthatóságú kódot hozhatunk létre a szinkron és aszinkron funkciók tekintetében.
Az @Async figyelmeztetések
Az @Async funkció használatához az @EnableAsync annotációt kell deklarálni, és ha nem állítunk be semmit külön, akkor proxy módban fog működni. Vagyis az @Async annotációval ellátott aszinkron metódusok mind a Spring AOP korlátozásait követik. A részletekért lásd a következő bejegyzést: ez a bejegyzés.
- Ha a private metódusra az @Async annotációt helyezzük, akkor az AOP nem fog működni.
- Ugyanazon objektumon belüli metódushívások esetén az AOP nem fog működni.
Hozzászólások0