परिचय
भाग I में , हमने समीक्षा की कि थ्रेड्स कैसे बनाए जाते हैं। आइए एक बार और याद करें।
एक थ्रेड को थ्रेड क्लास द्वारा दर्शाया जाता है, जिसकी
run()
विधि को कॉल किया जाता है। तो चलिए
Tutorialspoint ऑनलाइन जावा कंपाइलर का उपयोग करते हैं और निम्नलिखित कोड निष्पादित करते हैं:
public class HelloWorld {
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Hello World");
};
new Thread(task).start();
}
}
क्या थ्रेड पर कार्य शुरू करने का यही एकमात्र विकल्प है?
java.util.concurrent.Callable
यह पता चला है कि
java.lang.Runnable का एक भाई है जिसे
java.util.concurrent.Callable कहा जाता है जो Java 1.5 में दुनिया में आया। क्या अंतर हैं? यदि आप इस इंटरफ़ेस के लिए Javadoc को करीब से देखते हैं, तो हम देखते हैं कि इसके विपरीत
Runnable
, नया इंटरफ़ेस एक
call()
ऐसी विधि की घोषणा करता है जो एक परिणाम देता है। साथ ही, यह डिफ़ॉल्ट रूप से अपवाद फेंकता है। यही है, यह हमें
try-catch
चेक किए गए अपवादों को ब्लॉक करने से बचाता है। बुरा नहीं है, है ना? अब हमारे पास इसके बजाय एक नया कार्य है
Runnable
:
Callable task = () -> {
return "Hello, World!";
};
लेकिन हम इससे क्या करते हैं? हमें परिणाम देने वाले थ्रेड पर चलने वाले कार्य की आवश्यकता क्यों है? जाहिर है, भविष्य में किए गए किसी भी कार्य के लिए, हम भविष्य में उन कार्यों के परिणाम प्राप्त करने की अपेक्षा करते हैं। और हमारे पास संबंधित नाम वाला इंटरफ़ेस है:
java.util.concurrent.Future
java.util.concurrent.Future
Java.util.concurrent.Future इंटरफ़ेस उन कार्यों के साथ काम करने
के लिए एक एपीआई परिभाषित करता है जिनके परिणाम हम भविष्य में प्राप्त करने की योजना बनाते हैं: परिणाम प्राप्त करने के तरीके और स्थिति की जांच करने के तरीके। के संबंध में
Future
, हम
java.util.concurrent.FutureTask वर्ग में इसके कार्यान्वयन में रुचि रखते हैं। यह "कार्य" है जिसे
Future
. इस कार्यान्वयन को और भी दिलचस्प बनाता है कि यह रननेबल को भी लागू करता है। आप इसे थ्रेड्स पर कार्यों के साथ काम करने के पुराने मॉडल और नए मॉडल के बीच एक प्रकार का एडेप्टर मान सकते हैं (इस अर्थ में कि यह जावा 1.5 में दिखाई दिया)। यहाँ एक उदाहरण है:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class HelloWorld {
public static void main(String[] args) throws Exception {
Callable task = () -> {
return "Hello, World!";
};
FutureTask<String> future = new FutureTask<>(task);
new Thread(future).start();
System.out.println(future.get());
}
}
जैसा कि आप उदाहरण से देख सकते हैं, हम
get
कार्य से परिणाम प्राप्त करने के लिए विधि का उपयोग करते हैं।
टिप्पणी:जब आप विधि का उपयोग करके परिणाम प्राप्त करते हैं
get()
, तो निष्पादन तुल्यकालिक हो जाता है! आपको क्या लगता है कि यहां किस तंत्र का उपयोग किया जाएगा? सच है, कोई सिंक्रनाइज़ेशन ब्लॉक नहीं है। इसलिए हम JVisualVM में
WAITING कोmonitor
या के रूप में नहीं
wait
, बल्कि परिचित
park()
विधि के रूप में देखेंगे (क्योंकि
LockSupport
तंत्र का उपयोग किया जा रहा है)।
कार्यात्मक इंटरफेस
अगला, हम जावा 1.8 से कक्षाओं के बारे में बात करेंगे, इसलिए हम एक संक्षिप्त परिचय प्रदान करने के लिए अच्छा करेंगे। निम्नलिखित कोड को देखें:
Supplier<String> supplier = new Supplier<String>() {
@Override
public String get() {
return "String";
}
};
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
Function<String, Integer> converter = new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.valueOf(s);
}
};
बहुत सारे और बहुत सारे अतिरिक्त कोड, क्या आप नहीं कहेंगे? प्रत्येक घोषित वर्ग एक कार्य करता है, लेकिन हम इसे परिभाषित करने के लिए अतिरिक्त सहायक कोड का एक गुच्छा उपयोग करते हैं। और इस तरह जावा डेवलपर्स ने सोचा। तदनुसार, उन्होंने "कार्यात्मक इंटरफेस" ( ) का एक सेट पेश किया
@FunctionalInterface
और फैसला किया कि अब जावा स्वयं "सोच" करेगा, हमारे लिए चिंता करने के लिए केवल महत्वपूर्ण चीजें छोड़कर:
Supplier<String> supplier = () -> "String";
Consumer<String> consumer = s -> System.out.println(s);
Function<String, Integer> converter = s -> Integer.valueOf(s);
ए
Supplier
आपूर्ति करता है। इसका कोई पैरामीटर नहीं है, लेकिन यह कुछ देता है। इस तरह यह चीजों की आपूर्ति करता है। क
Consumer
सेवन करता है। यह एक इनपुट (एक तर्क) के रूप में कुछ लेता है और इसके साथ कुछ करता है। तर्क वह है जो वह खाता है। फिर हमारे पास भी है
Function
। यह इनपुट (तर्क) लेता है, कुछ करता है और कुछ देता है। आप देख सकते हैं कि हम सक्रिय रूप से जेनरिक का उपयोग कर रहे हैं।
यदि आप अनिश्चित हैं, तो आप " जावा में जेनरिक: प्रैक्टिस में एंगल्ड ब्रैकेट का उपयोग कैसे करें " पढ़कर एक पुनश्चर्या प्राप्त कर सकते हैं।
पूर्ण करने योग्य भविष्य
CompletableFuture
समय बीतता गया और जावा 1.8 नामक एक नई कक्षा प्रकट हुई। यह
Future
इंटरफ़ेस को लागू करता है, यानी भविष्य में हमारे कार्य पूरे हो जाएंगे, और हम
get()
परिणाम प्राप्त करने के लिए कॉल कर सकते हैं। लेकिन यह
CompletionStage
इंटरफ़ेस को भी लागू करता है। नाम ही सब कुछ कह देता है: यह गणनाओं के कुछ सेट की एक निश्चित अवस्था है। विषय का एक संक्षिप्त परिचय यहाँ समीक्षा में पाया जा सकता है: कंप्लीटेशन स्टेज और कम्प्लीटेबल फ्यूचर का परिचय। चलिए सीधे मुद्दे पर आते हैं। आइए उपलब्ध स्थिर तरीकों की सूची देखें जो हमें आरंभ करने में मदद करेंगे:
यहां उनका उपयोग करने के विकल्प दिए गए हैं:
import java.util.concurrent.CompletableFuture;
public class App {
public static void main(String[] args) throws Exception {
// A CompletableFuture that already contains a Result
CompletableFuture<String> completed;
completed = CompletableFuture.completedFuture("Just a value");
// A CompletableFuture that runs a new thread from Runnable. That's why it's Void
CompletableFuture<Void> voidCompletableFuture;
voidCompletableFuture = CompletableFuture.runAsync(() -> {
System.out.println("run " + Thread.currentThread().getName());
});
// A CompletableFuture that starts a new thread whose result we'll get from a Supplier
CompletableFuture<String> supplier;
supplier = CompletableFuture.supplyAsync(() -> {
System.out.println("supply " + Thread.currentThread().getName());
return "Value";
});
}
}
यदि हम इस कोड को निष्पादित करते हैं, तो हम देखेंगे कि a
CompletableFuture
भी बनाने में पूरी पाइपलाइन लॉन्च करना शामिल है। इसलिए, Java8 से स्टीमएपीआई के साथ एक निश्चित समानता के साथ, यह वह जगह है जहां हम इन दृष्टिकोणों के बीच अंतर पाते हैं। उदाहरण के लिए:
List<String> array = Arrays.asList("one", "two");
Stream<String> stringStream = array.stream().map(value -> {
System.out.println("Executed");
return value.toUpperCase();
});
यह जावा 8 के स्ट्रीम एपीआई का एक उदाहरण है। यदि आप यह कोड चलाते हैं, तो आप देखेंगे कि "निष्पादित" प्रदर्शित नहीं होगा। दूसरे शब्दों में, जब जावा में एक स्ट्रीम बनाई जाती है, तो स्ट्रीम तुरंत शुरू नहीं होती है। इसके बजाय, यह प्रतीक्षा करता है कि कोई इससे मूल्य चाहता है। लेकिन
CompletableFuture
किसी के लिए मूल्य पूछने के लिए इंतजार किए बिना तुरंत पाइपलाइन को निष्पादित करना शुरू कर देता है। मुझे लगता है कि यह समझना महत्वपूर्ण है। तो, हमारे पास एक
CompletableFuture
. हम एक पाइपलाइन (या श्रृंखला) कैसे बना सकते हैं और हमारे पास क्या तंत्र हैं? उन कार्यात्मक इंटरफेस को याद करें जिनके बारे में हमने पहले लिखा था।
- हमारे पास a है
Function
जो A लेता है और B लौटाता है। इसकी एक ही विधि है apply()
:।
- हमारे पास a है
Consumer
जो A लेता है और कुछ भी नहीं देता है (शून्य)। इसकी एक ही विधि है: accept()
.
- हमारे पास है
Runnable
, जो धागे पर चलता है, और कुछ भी नहीं लेता है और कुछ भी वापस नहीं करता है। इसकी एक ही विधि है: run()
.
याद रखने वाली अगली बात यह है कि वह अपने काम में , , और
CompletableFuture
का उपयोग करता है । तदनुसार, आप हमेशा जान सकते हैं कि आप निम्न के साथ निम्न कार्य कर सकते हैं :
Runnable
Consumers
Functions
CompletableFuture
public static void main(String[] args) throws Exception {
AtomicLong longValue = new AtomicLong(0);
Runnable task = () -> longValue.set(new Date().getTime());
Function<Long, Date> dateConverter = (longvalue) -> new Date(longvalue);
Consumer<Date> printer = date -> {
System.out.println(date);
System.out.flush();
};
// CompletableFuture computation
CompletableFuture.runAsync(task)
.thenApply((v) -> longValue.get())
.thenApply(dateConverter)
.thenAccept(printer);
}
, , और विधियों
thenRun()
के "Async" संस्करण हैं। इसका मतलब है कि इन चरणों को एक अलग थ्रेड पर पूरा किया जाएगा। यह धागा एक विशेष पूल से लिया जाएगा — इसलिए हम पहले से नहीं जान पाएंगे कि यह नया धागा होगा या पुराना। यह सब इस बात पर निर्भर करता है कि कार्य कितने कम्प्यूटेशनल-गहन हैं। इन विधियों के अलावा, तीन और दिलचस्प संभावनाएँ हैं। स्पष्टता के लिए, आइए कल्पना करें कि हमारे पास एक निश्चित सेवा है जो कहीं से किसी प्रकार का संदेश प्राप्त करती है - और इसमें समय लगता है:
thenApply()
thenAccept()
public static class NewsService {
public static String getMessage() {
try {
Thread.currentThread().sleep(3000);
return "Message";
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
CompletableFuture
अब, प्रदान की जाने वाली अन्य योग्यताओं पर एक नज़र डालते हैं । हम a के परिणाम को
CompletableFuture
दूसरे के परिणाम के साथ जोड़ सकते हैं
CompletableFuture
:
Supplier newsSupplier = () -> NewsService.getMessage();
CompletableFuture<String> reader = CompletableFuture.supplyAsync(newsSupplier);
CompletableFuture.completedFuture("!!")
.thenCombine(reader, (a, b) -> b + a)
.thenAccept(result -> System.out.println(result))
.get();
ध्यान दें कि धागे डिफ़ॉल्ट रूप से डेमॉन धागे हैं, इसलिए स्पष्टता के लिए, हम
get()
परिणाम की प्रतीक्षा करने के लिए उपयोग करते हैं। न केवल हम गठबंधन कर सकते हैं
CompletableFutures
, हम एक वापस भी कर सकते हैं
CompletableFuture
:
CompletableFuture.completedFuture(2L)
.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
.thenAccept(result -> System.out.println(result));
यहाँ मैं ध्यान देना चाहता हूँ कि इस
CompletableFuture.completedFuture()
विधि का उपयोग संक्षिप्तता के लिए किया गया था। यह विधि एक नया थ्रेड नहीं बनाती है, इसलिए शेष पाइपलाइन को उसी थ्रेड पर निष्पादित किया जाएगा जहां
completedFuture
कॉल किया गया था। एक
thenAcceptBoth()
विधि भी होती है। यह बहुत समान है
accept()
, लेकिन यदि
thenAccept()
a को स्वीकार करता है
Consumer
,
thenAcceptBoth()
तो दूसरे
CompletableStage
+ को
BiConsumer
इनपुट के रूप में स्वीकार करता है, यानी a
consumer
जो एक के बजाय 2 स्रोत लेता है। विधियों द्वारा प्रदान की जाने वाली एक और दिलचस्प क्षमता है जिसका नाम "या तो" शब्द शामिल है:
ये विधियां एक विकल्प को स्वीकार करती हैं
CompletableStage
और उस पर निष्पादित होती हैं
CompletableStage
जिसे पहले निष्पादित किया जाता है। अंत में, मैं इस समीक्षा को एक और दिलचस्प विशेषता के साथ समाप्त करना चाहता हूं
CompletableFuture
: त्रुटि प्रबंधन।
CompletableFuture.completedFuture(2L)
.thenApply((a) -> {
throw new IllegalStateException("error");
}).thenApply((a) -> 3L)
//.exceptionally(ex -> 0L)
.thenAccept(val -> System.out.println(val));
यह कोड कुछ नहीं करेगा, क्योंकि अपवाद होगा और कुछ नहीं होगा। लेकिन "असाधारण रूप से" कथन पर टिप्पणी न करके, हम अपेक्षित व्यवहार को परिभाषित करते हैं। की बात करते हुए
CompletableFuture
, मैं आपको निम्नलिखित वीडियो देखने की भी सलाह देता हूं:
मेरी विनम्र राय में, ये इंटरनेट पर सबसे अधिक व्याख्यात्मक वीडियो हैं। उन्हें यह स्पष्ट करना चाहिए कि यह सब कैसे काम करता है, हमारे पास कौन सा टूलकिट उपलब्ध है और इसकी आवश्यकता क्यों है।
निष्कर्ष
उम्मीद है, अब यह स्पष्ट हो गया है कि गणना पूरी होने के बाद आप थ्रेड्स का उपयोग कैसे कर सकते हैं। अतिरिक्त सामग्री:
बेहतर एक साथ: जावा और थ्रेड क्लास। भाग I - थ्रेड्स ऑफ़ एक्जीक्यूशन बेहतर एक साथ: जावा और थ्रेड क्लास। भाग II - तुल्यकालन बेहतर एक साथ: जावा और थ्रेड वर्ग। भाग III - इंटरेक्शन बेटर टुगेदर: जावा और थ्रेड क्लास। भाग V - एक्ज़ीक्यूटर, थ्रेडपूल, फोर्क / जॉइन बेटर टुगेदर: जावा और थ्रेड क्लास। भाग VI - आग बुझाओ!
GO TO FULL VERSION