제이온

[สปริง] วิธีการใช้ @Async

สร้าง: 2024-04-25

สร้าง: 2024-04-25 22:33

การประมวลผลแบบอะซิงโครนัสใน Java

ก่อนที่จะดู Spring @Async เราจำเป็นต้องเข้าใจแนวคิดของการประมวลผลแบบซิงโครนัส อะซิงโครนัส และมัลติเธรดก่อน ซึ่งเราจะถือว่าผู้อ่านมีความรู้พื้นฐานในส่วนนี้แล้ว และจะเริ่มต้นด้วยการดูวิธีการประมวลผลแบบอะซิงโครนัสใน Java แบบพื้นฐานผ่านโค้ด หากผู้อ่านมีความคุ้นเคยกับเธรดของ Java แล้ว สามารถข้ามบทนี้ไปได้



หากเราต้องการสร้างฟังก์ชันการพิมพ์ข้อความแบบซิงโครนัส เราสามารถเขียนโค้ดได้ดังตัวอย่างข้างต้น การเปลี่ยนโค้ดนี้ให้เป็นแบบอะซิงโครนัสด้วยมัลติเธรด สามารถทำได้ดังนี้



แต่การใช้วิธีนี้มีความเสี่ยงสูงเนื่องจากเราไม่สามารถจัดการเธรดได้ ตัวอย่างเช่น หากมีการเรียกใช้ฟังก์ชัน 10,000 ครั้งพร้อมกัน ระบบจะต้องสร้างเธรด 10,000 ตัวในเวลาอันสั้น ซึ่งการสร้างเธรดนั้นใช้ทรัพยากรค่อนข้างมาก อาจส่งผลเสียต่อประสิทธิภาพของโปรแกรม และอาจเกิดข้อผิดพลาด OOM ได้ ดังนั้น เราจึงต้องใช้ Thread Pool ในการจัดการเธรด และ Java มี ExecutorService class ไว้ให้บริการ



เราจำกัดจำนวนเธรดทั้งหมดไว้ที่ 10 และสามารถประมวลผลแบบอะซิงโครนัสด้วยมัลติเธรดได้อย่างถูกต้อง แต่การใช้วิธีนี้จะต้องใช้ submit() method ของ ExecutorService ในแต่ละ method ที่ต้องการประมวลผลแบบอะซิงโครนัส ทำให้ต้องแก้ไขโค้ดซ้ำๆ นั่นคือ หากเราต้องการเปลี่ยน method ที่เขียนด้วยลอจิกแบบซิงโครนัสให้เป็นแบบอะซิงโครนัส เราจะต้องแก้ไขลอจิกของ method นั้นๆ


Spring @Async

วิธีการง่ายๆ


เพียงแค่เพิ่ม @EnableAsync annotation บน Application class และเพิ่ม @Async annotation บน method ที่ต้องการประมวลผลแบบอะซิงโครนัส เท่านี้ก็เสร็จแล้ว แต่การใช้วิธีนี้มีปัญหาคือ ไม่ได้มีการจัดการเธรด เนื่องจากการตั้งค่าเริ่มต้นของ @Async คือการใช้ SimpleAsyncTaskExecutor ซึ่งไม่ใช่ thread pool แต่ทำหน้าที่สร้างเธรดขึ้นมาเท่านั้น


วิธีการใช้ Thread Pool

ก่อนอื่น ให้ลบ @EnableAsync ออกจาก Application class เนื่องจาก Application class มีการตั้งค่า @EnableAutoConfiguration หรือ @SpringBootApplication ซึ่งในขณะรันไทม์จะอ่านข้อมูล bean ของ threadPoolTaskExecutor (ซึ่งเราจะสร้างขึ้นในภายหลัง) จาก SpringAsyncConfig class (ซึ่งเราจะสร้างขึ้นในภายหลัง)



เราสามารถกำหนด core และ max size ได้ แต่สิ่งที่เราคาดหวังไว้ว่า เมื่อเริ่มต้นระบบจะมีเธรดทำงาน core size ตัว และเมื่อทำงานเกิน core size จะเพิ่มเธรดขึ้นไปจนถึง max size นั้นไม่เป็นความจริง


ภายในระบบนั้นจะมีการสร้าง LinkedBlockingQueue ที่มีขนาด Integer.MAX_VALUE โดยค่าเริ่มต้น เมื่อ core size ไม่สามารถประมวลผลงานได้ทัน จะนำงานไปใส่ใน Queue ก่อน และเมื่อ Queue เต็ม ก็จะเพิ่มเธรดขึ้นไปจนถึง max size เพื่อประมวลผล


หากเราไม่ต้องการให้ Queue มีขนาดใหญ่ถึง Integer.MAX_VALUE สามารถกำหนด queueCapacity ได้ ดังตัวอย่างข้างต้น เมื่อเริ่มต้นระบบจะมีเธรด 3 ตัวทำงาน และเมื่อทำงานเกิน 3 ตัว งานจะถูกนำไปใส่ใน Queue ขนาด 100 และเมื่อ Queue เต็ม จะเพิ่มเธรดขึ้นไปจนถึง 30 ตัว


เมื่อตั้งค่า Thread Pool เสร็จแล้ว เราสามารถกำหนดชื่อ bean ที่เราสร้างไว้ใน method ที่มี @Async annotation



หากต้องการกำหนด Thread Pool หลายๆ แบบ สามารถสร้าง method สร้าง bean เหมือนกับ threadPoolTaskExecutor() ได้หลายๆ method และกำหนดชื่อ bean ใน @Async ที่ต้องการใช้


รูปแบบการส่งคืนตามชนิดของค่าส่งคืน

กรณีที่ไม่มีค่าส่งคืน

กรณีที่ method ที่ต้องการประมวลผลแบบอะซิงโครนัสไม่จำเป็นต้องส่งผลลัพธ์กลับมา ในกรณีนี้ เราสามารถกำหนดชนิดของค่าส่งคืนของ @Async annotation เป็น void


กรณีที่มีค่าส่งคืน

สามารถใช้ Future, ListenableFuture, CompletableFuture เป็นชนิดของค่าส่งคืนได้ โดยเราจะต้องห่อผลลัพธ์ของ method แบบอะซิงโครนัสด้วย new AsyncResult()


[Future]


future.get() จะบล็อกการทำงานและรอจนกว่าจะได้รับผลลัพธ์ ทำให้การทำงานเป็นแบบซิงโครนัส จึงไม่ค่อยมีประสิทธิภาพ โดยทั่วไปแล้ว Future จะไม่ค่อยถูกนำมาใช้


[ListenableFuture]


ListenableFuture ช่วยให้เราสามารถประมวลผลงานได้แบบ non-blocking ผ่าน callback method addCallback() method ตัวแรกเป็น callback method ที่ทำงานเมื่อเสร็จสิ้น และตัวที่สองเป็น callback method ที่ทำงานเมื่อเกิดข้อผิดพลาด หมายเหตุ เนื่องจากเราตั้งค่า core thread ของ thread pool ไว้ที่ 3 ดังนั้นจึงเห็นข้อความ “Task Start” 3 ข้อความแรก



[CompletableFuture]

ถึงแม้ว่า ListenableFuture จะช่วยให้เราสามารถสร้างลอจิกแบบ non-blocking ได้ แต่หากจำเป็นต้องใช้ callback ซ้อน callback อาจทำให้เกิดโค้ดที่ซับซ้อนและอ่านยาก เรียกว่า callback hell


<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>


เนื่องจากหัวข้อนี้ไม่ได้เจาะลึกถึง CompletableFuture จึงขอแนะนำให้ผู้อ่านที่สนใจศึกษาโค้ดการแก้ปัญหา callback ซ้อน callback ดูจากลิงก์ด้านล่าง



โค้ดมีความอ่านง่ายกว่าการกำหนด callback ใน ListenableFuture และสามารถทำงานแบบ non-blocking ได้อย่างสมบูรณ์ ดังนั้น จึงแนะนำให้ใช้ CompletableFuture เมื่อต้องการค่าส่งคืนจาก @Async


ข้อดีของ @Async

ผู้พัฒนาสามารถเขียน method ด้วยลอจิกแบบซิงโครนัส และเมื่อต้องการเปลี่ยนเป็นแบบอะซิงโครนัส เพียงแค่เพิ่ม @Async annotation บน method เท่านั้น ทำให้โค้ดมีความยืดหยุ่นและง่ายต่อการดูแลรักษา


ข้อควรระวังของ @Async

ในการใช้ฟังก์ชัน @Async เราจำเป็นต้องประกาศ @EnableAsync annotation และหากไม่ได้มีการกำหนดค่าเพิ่มเติม ระบบจะทำงานในโหมด proxy นั่นคือ method แบบอะซิงโครนัสทั้งหมดที่ใช้ @Async จะต้องปฏิบัติตามข้อจำกัดของ Spring AOP สามารถศึกษาข้อมูลเพิ่มเติมได้จากโพสต์นี้


  • การเพิ่ม @Async บน method ที่มี access modifier เป็น private จะไม่สามารถใช้งาน AOP ได้
  • การเรียกใช้ method ภายใน object เดียวกัน จะไม่สามารถใช้งาน AOP ได้


แหล่งที่มา

ความคิดเห็น0

[สำหรับผู้ไม่ใช่ผู้เชี่ยวชาญ ด้านการพัฒนาซอฟต์แวร์ เพื่อความอยู่รอด] 14. สรุปเนื้อหาสัมภาษณ์ทางเทคนิคที่ผู้พัฒนาซอฟต์แวร์มือใหม่ถามบ่อยสรุปคำถามทางเทคนิคที่มักถามในการสัมภาษณ์งานผู้พัฒนาซอฟต์แวร์มือใหม่ (พื้นที่หน่วยความจำ โครงสร้างข้อมูล ฐานข้อมูล ฯลฯ) หวังว่าจะเป็นประโยชน์ในการเตรียมตัวสัมภาษณ์งานด้านการพัฒนา
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자
투잡뛰는 개발 노동자

April 3, 2024

ประสบการณ์การมีส่วนร่วมโอเพนซอร์สบทความนี้แบ่งปันประสบการณ์การมีส่วนร่วมโอเพนซอร์ส ช่วยสร้างแรงบันดาลใจให้เอาชนะความกลัวและก้าวแรก เริ่มต้นจากการปรับปรุงเล็กๆ น้อยๆ แล้วค่อยๆ สร้างความมั่นใจและเติบโต
seungwon
seungwon
seungwon
seungwon

May 3, 2025

สัญญาบนพื้นฐานบล็อกเชนเนื้อหาครอบคลุมเกี่ยวกับแนวคิด สาระสำคัญ กระบวนการใช้งาน ความปลอดภัย และประเด็นทางกฎหมาย ตลอดจนอนาคตของสัญญาอัจฉริยะบนพื้นฐานบล็อกเชน พร้อมยกตัวอย่างการใช้งานในหลากหลายสาขา เช่น การเงิน อสังหาริมทรัพย์ และอื่นๆ รวมถึงการนำเสนอข้อจำกัดทางเทคนิคและแนวทางแ
Cherry Bee
Cherry Bee
Cherry Bee
Cherry Bee

March 23, 2025

29 พ.ย. 2567 ข่าวล่าช้า: สมัคร Replit แบบชำระเงิน / นิสัยการพัฒนาซอฟต์แวร์ที่ดีบทความบล็อกที่เขียนเมื่อวันที่ 29 พฤศจิกายน พ.ศ. 2567 นี้จะแบ่งปันรีวิวการสมัครสมาชิกแบบชำระเงินของ Replit และแนะนำนิสัยการพัฒนาซอฟต์แวร์ที่ดี 10 ประการ ครอบคลุมกลยุทธ์การเขียนโค้ดและการปรับโครงสร้างที่มีประสิทธิภาพ รวมถึงความสำคัญของการทดสอบ
Charles Lee
Charles Lee
Charles Lee
Charles Lee

November 29, 2024

[คอลัมน์โดยฮอ ยองจู] ทำไม Meta ถึงสร้าง SNS ใหม่ 'Threads'?SNS ใหม่ 'Threads' ของ Meta สร้างขึ้นเพื่อการสื่อสารที่เบา สบาย และเป็นอิสระ และได้รับความนิยมในกลุ่มคนที่ต้องการการสื่อสารที่อบอุ่นเป็นกันเอง
허영주
허영주
허영주
허영주

June 18, 2024

LLM สำหรับเด็กประถมคำอธิบายแนวคิด LLM ที่แม้แต่เด็กประถมก็เข้าใจ! LLM คือ AI ที่ตอบคำถามเป็นข้อความเมื่อได้รับคำถามเป็นข้อความ สามารถทำงานได้หลากหลาย เช่น การเขียนโค้ด การวิเคราะห์ภาพ ฯลฯ ปัจจุบันนักพัฒนาใช้ AI เป็นเครื่องมือ
Sunrabbit
Sunrabbit
Sunrabbit
Sunrabbit

March 4, 2025