![translation](https://cdn.durumis.com/common/trans.png)
यह एक AI अनुवादित पोस्ट है।
भाषा चुनें
durumis AI द्वारा संक्षेपित पाठ
- यह लेख एसिंक्रोनस प्रोसेसिंग को लागू करने के तरीके के बारे में बताता है जिसमें @Async एनोटेशन का उपयोग किया जाता है, विशेष रूप से स्प्रिंग में @Async का उपयोग करते समय थ्रेड पूल को कॉन्फ़िगर करने के तरीके पर विस्तार से बताया गया है।
- इसके अलावा, यह लेख @Async का उपयोग करते समय रिटर्न प्रकार के आधार पर वापसी स्वरूप को Future, ListenableFuture, CompletableFuture आदि में विभाजित करके बताता है और प्रत्येक प्रकार की विशेषताओं और फायदे और नुकसान की तुलनात्मक विश्लेषण करता है।
- अंत में, यह लेख @Async के फायदे और सावधानियों का परिचय देता है, यह बताता है कि private मेथड में @Async लगाने पर AOP काम नहीं करता है और साथ ही यह भी बताया गया है कि एक ही ऑब्जेक्ट के मेथड को आपस में कॉल करने पर AOP काम नहीं करता है।
जावा अतुल्यकालिक प्रसंस्करण
स्प्रिंग @Async देखने से पहले, सिंक्रोनस, अतुल्यकालिक और मल्टीथ्रेडिंग की अवधारणाएँ अनिवार्य हैं। हम मानते हैं कि आप इन अवधारणाओं से परिचित हैं, और कोड के साथ शुद्ध जावा अतुल्यकालिक प्रसंस्करण विधि पर एक नज़र डालते हैं। यदि आप जावा थ्रेड से परिचित हैं, तो आप इस अध्याय को छोड़ सकते हैं।
public class MessageService {
public void print(String message) {
System.out.println(message);
}
}
public class Main {
public static void main(String[] args) {
MessageService messageService = new MessageService();
for (int i = 1; i <= 100; i++) {
messageService.print(i + "");
}
}
यदि आप संदेश प्राप्त करने और केवल इसे आउटपुट करने की सुविधा को सिंक्रोनस तरीके से बनाते हैं, तो आप ऊपर दिए गए कोड को इस तरह लिख सकते हैं। इसे मल्टीथ्रेडिंग अतुल्यकालिक तरीके से बदलने पर, आप स्रोत कोड को इस तरह लिख सकते हैं।
public class MessageService {
public void print(String message) {
new Thread(() -> System.out.println(message))
.start();
}
}
public class Main {
public static void main(String[] args) {
MessageService messageService = new MessageService();
for (int i = 1; i <= 100; i++) {
messageService.print(i + "");
}
}
हालाँकि, इस विधि में थ्रेड को प्रबंधित करने में असमर्थता है, जो इसे बहुत खतरनाक बनाता है। उदाहरण के लिए, यदि 10,000 कॉल एक साथ होते हैं, तो आपको बहुत कम समय में 10,000 थ्रेड बनाने होंगे। थ्रेड बनाने की लागत कम नहीं है, इसलिए यह प्रोग्राम के प्रदर्शन को नकारात्मक रूप से प्रभावित करता है, और यहां तक कि OOM त्रुटि भी हो सकती है। इसलिए, थ्रेड को प्रबंधित करने के लिए, आपको थ्रेड पूल को लागू करना होगा, और जावा ExecutorService वर्ग प्रदान करता है।
public class MessageService {
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
public void print(String message) {
executorService.submit(() -> System.out.println(message));
}
}
public class Main {
public static void main(String[] args) {
MessageService messageService = new MessageService();
for (int i = 1; i <= 100; i++) {
messageService.print(i + "");
}
}
हमने थ्रेड की कुल संख्या को 10 तक सीमित कर दिया है, और अब हम सही ढंग से वांछित मल्टीथ्रेडिंग अतुल्यकालिक प्रसंस्करण कर सकते हैं। हालाँकि, उपरोक्त विधि को अतुल्यकालिक रूप से संसाधित किए जाने वाले प्रत्येक विधि में ExecutorService की submit() विधि लागू करने की आवश्यकता होती है, जिससे बार-बार संशोधन करने पड़ते हैं। दूसरे शब्दों में, यदि आप शुरुआत में सिंक्रोनस लॉजिक के साथ लिखे गए विधि को अतुल्यकालिक में बदलना चाहते हैं, तो विधि के स्वयं के तर्क को बदलना अनिवार्य है।
स्प्रिंग @Async
सरल विधि
@EnableAsync
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@Service
public class MessageService {
@Async
public void print(String message) {
System.out.println(message);
}
}
@RequiredArgsConstructor
@RestController
public class MessageController {
private final MessageService messageService;
@GetMapping("/messages")
@ResponseStatus(code = HttpStatus.OK)
public void printMessage() {
for (int i = 1; i <= 100; i++) {
messageService.print(i + "");
}
}
@EnableAsync एनोटेशन को Application क्लास पर रखें, और @Async एनोटेशन को सिंक्रोनस लॉजिक के विधि पर रखें जिसे आप अतुल्यकालिक रूप से संसाधित करना चाहते हैं। लेकिन, इस विधि में थ्रेड को प्रबंधित न करने की समस्या है। ऐसा इसलिए है क्योंकि @Async का डिफ़ॉल्ट कॉन्फ़िगरेशन SimpleAsyncTaskExecutor का उपयोग करना है, जो एक थ्रेड पूल नहीं है, बल्कि केवल थ्रेड बनाने का काम करता है।
थ्रेड पूल का उपयोग करने की विधि
सबसे पहले, Application क्लास से @EnableAsync हटा दें। यदि Application क्लास में @EnableAutoConfiguration या @SpringBootApplication सेटिंग है, तो रनटाइम पर @Configuration के साथ SpringAsyncConfig क्लास (जिसे हम नीचे बनाएंगे) की threadPoolTaskExecutor बीन जानकारी पढ़ी जाएगी।
@Configuration
@EnableAsync // Application नहीं, बल्कि Async सेटिंग क्लास पर लगाना चाहिए।
public class SpringAsyncConfig {
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(3); // डिफ़ॉल्ट थ्रेड की संख्या
taskExecutor.setMaxPoolSize(30); // अधिकतम थ्रेड की संख्या
taskExecutor.setQueueCapacity(100); // क्यू आकार
taskExecutor.setThreadNamePrefix("Executor-");
return taskExecutor;
}
आप कोर और मैक्स आकार निर्दिष्ट कर सकते हैं। इस मामले में, यह अनुमान लगाया जा सकता है कि यह शुरू में कोर आकार के रूप में काम करेगा, और यदि यह अधिक काम संभाल नहीं सकता है, तो अधिकतम आकार तक थ्रेड बढ़ जाएगा, लेकिन ऐसा नहीं है।
आंतरिक रूप से, यह Integer.MAX_VALUE आकार का LinkedBlockingQueue बनाता है, इसलिए यदि कोर आकार के थ्रेड काम संभाल नहीं सकते हैं, तो वे क्यू में इंतजार करते हैं। क्यू भरा हुआ होने पर, यह अधिकतम आकार तक थ्रेड बनाता है और काम संभालता है।
यदि आप Integer.MAX_VALUE पर क्यू आकार निर्दिष्ट करने से बचने के लिए, आप queueCapacity निर्दिष्ट कर सकते हैं। ऊपर दिए गए सेटिंग के साथ, यह शुरू में 3 थ्रेड के साथ काम करेगा, और यदि प्रसंस्करण गति धीमी हो जाती है, तो यह कार्य को 100 आकार के क्यू में रख देगा, और यदि कोई और अनुरोध आता है, तो यह अधिकतम 30 थ्रेड बनाएगा और काम संभालेगा।
थ्रेड पूल सेटिंग पूरी हो जाने के बाद, आपको @Async एनोटेशन के साथ विधि में ऊपर दिए गए बीन के नाम को संलग्न करना होगा।
@Service
public class MessageService {
@Async("threadPoolTaskExecutor")
public void print(String message) {
System.out.println(message);
}
यदि आप कई प्रकार के थ्रेड पूल सेट करना चाहते हैं, तो आपको threadPoolTaskExecutor() जैसी कई बीन बनाने की विधियाँ बनानी होंगी, और @Async सेटिंग करते समय, आप वह थ्रेड पूल बीन डाल सकते हैं जिसे आप चाहते हैं।
रिटर्न टाइप द्वारा लौटाया गया प्रारूप
कोई रिटर्न वैल्यू नहीं है
यह तब होता है जब एक विधि जिसे अतुल्यकालिक रूप से संसाधित किया जाना चाहिए, को प्रसंस्करण परिणाम देने की आवश्यकता नहीं होती है। इस मामले में, आप @Async एनोटेशन का रिटर्न टाइप void पर सेट कर सकते हैं।
रिटर्न वैल्यू है
आप Future, ListenableFuture और CompletableFuture प्रकार को रिटर्न टाइप के रूप में उपयोग कर सकते हैं। आप अतुल्यकालिक विधि के रिटर्न प्रारूप को new AsyncResult() में बांध सकते हैं।
[फ़्यूचर]
@Service
public class MessageService {
@Async
public Future print(String message) throws InterruptedException {
System.out.println("Task Start - " + message);
Thread.sleep(3000);
return new AsyncResult<>("jayon-" + message);
}
}
@RequiredArgsConstructor
@RestController
public class MessageController {
private final MessageService messageService;
@GetMapping("/messages")
@ResponseStatus(code = HttpStatus.OK)
public void printMessage() throws ExecutionException, InterruptedException {
for (int i = 1; i <= 5; i++) {
Future future = messageService.print(i + "");
System.out.println(future.get());
}
}
}
// निष्पादन परिणाम
Task Start - 1
jayon-1
Task Start - 2
jayon-2
Task Start - 3
jayon-3
Task Start - 4
jayon-4
Task Start - 5
future.get() अनुरोध परिणाम के आने तक ब्लॉकिंग के माध्यम से प्रतीक्षा करने का काम करता है। इसलिए यह अतुल्यकालिक ब्लॉकिंग विधि बन जाती है, जिसके परिणामस्वरूप प्रदर्शन खराब होता है। आमतौर पर, फ़्यूचर का उपयोग नहीं किया जाता है।
[ListenableFuture]
@Service
public class MessageService {
@Async
public ListenableFuture print(String message) throws InterruptedException {
System.out.println("Task Start - " + message);
Thread.sleep(3000);
return new AsyncResult<>("jayon-" + message);
}
}
@RequiredArgsConstructor
@RestController
public class MessageController {
private final MessageService messageService;
@GetMapping("/messages")
@ResponseStatus(code = HttpStatus.OK)
public void printMessage() throws InterruptedException {
for (int i = 1; i <= 5; i++) {
ListenableFuture listenableFuture = messageService.print(i + "");
listenableFuture.addCallback(System.out::println, error -> System.out.println(error.getMessage()));
}
}
}
// निष्पादन परिणाम
Task Start - 1
Task Start - 3
Task Start - 2
jayon-1
jayon-2
Task Start - 5
jayon-3
Task Start - 4
jayon-4
ListenableFuture कॉलबैक के माध्यम से गैर-अवरोधक तरीके से कार्यों को संसाधित करने की अनुमति देता है। addCallback() विधि का पहला पैरामीटर कार्य पूरा होने पर कॉलबैक विधि है, और दूसरा पैरामीटर कार्य विफलता पर कॉलबैक विधि है। ध्यान दें कि थ्रेड पूल का कोर थ्रेड 3 पर सेट किया गया है, इसलिए आप देख सकते हैं कि "कार्य प्रारंभ" संदेश शुरू में 3 बार मुद्रित होता है।
[CompletableFuture]
ListenableFuture के साथ भी, आप गैर-अवरोधक तर्क को लागू कर सकते हैं, लेकिन यदि आपको कॉलबैक के अंदर कॉलबैक की आवश्यकता होती है, तो यह बहुत जटिल कोड का कारण बनता है जिसे कॉलबैक नर्क कहा जाता है।
बेशक, इस बार हम CompletableFuture को विस्तार से नहीं देखेंगे, इसलिए यदि आप जटिल कॉलबैक को संभालने वाले कोड के बारे में उत्सुक हैं, तो कृपया नीचे दिए गए स्रोत लिंक देखें।
@Service
public class MessageService {
@Async
public CompletableFuture print(String message) throws InterruptedException {
System.out.println("Task Start - " + message);
Thread.sleep(3000);
return new AsyncResult<>("jayon-" + message).completable();
}
}
@RequiredArgsConstructor
@RestController
public class MessageController {
private final MessageService messageService;
@GetMapping("/messages")
@ResponseStatus(code = HttpStatus.OK)
public void printMessage() throws InterruptedException {
for (int i = 1; i <= 5; i++) {
CompletableFuture completableFuture = messageService.print(i + "");
completableFuture
.thenAccept(System.out::println)
.exceptionally(error -> {
System.out.println(error.getMessage());
return null;
});
}
}
ListenableFuture की कॉलबैक परिभाषा की तुलना में पठनीयता बेहतर है, और यह पूरी तरह से गैर-अवरोधक कार्य करता है। इसलिए, यदि आपको @Async का उपयोग करते समय रिटर्न वैल्यू की आवश्यकता होती है, तो CompletableFuture का उपयोग करने की अनुशंसा की जाती है।
@Async के लाभ
डेवलपर्स सिंक्रोनस तरीके से विधियों को लिख सकते हैं, और यदि वे अतुल्यकालिक चाहते हैं, तो उन्हें बस विधि के ऊपर @Async एनोटेशन जोड़ना होगा। इसलिए, आप सिंक्रोनस और अतुल्यकालिक के लिए अच्छे रखरखाव वाले कोड बना सकते हैं।
@Async के लिए सावधानियां
@Async कार्यक्षमता का उपयोग करने के लिए, आपको @EnableAsync एनोटेशन घोषित करना होगा, लेकिन यदि आप कोई विशेष कॉन्फ़िगरेशन नहीं करते हैं, तो यह प्रॉक्सी मोड में काम करता है। दूसरे शब्दों में, @Async एनोटेशन के साथ काम करने वाली अतुल्यकालिक विधियाँ सभी स्प्रिंग AOP बाधाओं का पालन करती हैं। अधिक जानकारी के लिए, कृपयायह पोस्टदेखें।
- यदि आप एक निजी विधि पर @Async लगाते हैं, तो AOP काम नहीं करेगा।
- यदि आप एक ही ऑब्जेक्ट के अंदर विधियों को एक दूसरे को कॉल करते हैं, तो AOP काम नहीं करेगा।