Java Language
समवर्ती प्रोग्रामिंग (थ्रेड्स)
खोज…
परिचय
समवर्ती संगणना अभिकलन का एक रूप है जिसमें क्रमिक रूप से कई संगणनाएँ समवर्ती रूप से निष्पादित की जाती हैं। जावा भाषा को थ्रेड के उपयोग के माध्यम से समवर्ती प्रोग्रामिंग का समर्थन करने के लिए डिज़ाइन किया गया है। वस्तुओं और संसाधनों को कई धागों द्वारा पहुँचा जा सकता है; प्रत्येक थ्रेड प्रोग्राम में किसी भी ऑब्जेक्ट को संभावित रूप से एक्सेस कर सकता है और प्रोग्रामर को यह सुनिश्चित करना होगा कि ऑब्जेक्ट तक पहुंच और रीडिंग लिखना थ्रेड्स के बीच ठीक से सिंक्रोनाइज़ हो।
टिप्पणियों
संबंधित विषय (ओं) StackOverflow पर:
बेसिक मल्टीथ्रेडिंग
यदि आपके पास निष्पादित करने के लिए कई कार्य हैं, और ये सभी कार्य पूर्ववर्ती के परिणाम पर निर्भर नहीं हैं, तो आप अपने कंप्यूटर के लिए मल्टीथ्रेडिंग का उपयोग एक ही समय में और अधिक प्रोसेसर का उपयोग करके कर सकते हैं यदि आपका कंप्यूटर कर सकता है। यदि आपके पास कुछ बड़े स्वतंत्र कार्य हैं, तो यह आपके कार्यक्रम का निष्पादन तेजी से कर सकता है।
class CountAndPrint implements Runnable {
private final String name;
CountAndPrint(String name) {
this.name = name;
}
/** This is what a CountAndPrint will do */
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(this.name + ": " + i);
}
}
public static void main(String[] args) {
// Launching 4 parallel threads
for (int i = 1; i <= 4; i++) {
// `start` method will call the `run` method
// of CountAndPrint in another thread
new Thread(new CountAndPrint("Instance " + i)).start();
}
// Doing some others tasks in the main Thread
for (int i = 0; i < 10000; i++) {
System.out.println("Main: " + i);
}
}
}
विभिन्न CountAndPrint
इंस्टेंस के रन विधि का कोड गैर-पूर्वानुमानित क्रम में निष्पादित होगा। एक नमूना निष्पादन का एक टुकड़ा इस तरह लग सकता है:
Instance 4: 1
Instance 2: 1
Instance 4: 2
Instance 1: 1
Instance 1: 2
Main: 1
Instance 4: 3
Main: 2
Instance 3: 1
Instance 4: 4
...
निर्माता-उपभोक्त
निर्माता-उपभोक्ता समस्या समाधान का एक सरल उदाहरण। ध्यान दें कि JDK कक्षाएं ( AtomicBoolean
and BlockingQueue
) का उपयोग सिंक्रोनाइज़ेशन के लिए किया जाता है, जिससे अमान्य समाधान बनाने की संभावना कम हो जाती है। ब्लॉकिंगक्यू के विभिन्न प्रकारों के लिए जावदोक से परामर्श करें; अलग-अलग कार्यान्वयन को चुनने से इस उदाहरण के व्यवहार में परिवर्तन हो सकता है (जैसे कि DelayQueue या प्राथमिकता कतार )।
public class Producer implements Runnable {
private final BlockingQueue<ProducedData> queue;
public Producer(BlockingQueue<ProducedData> queue) {
this.queue = queue;
}
public void run() {
int producedCount = 0;
try {
while (true) {
producedCount++;
//put throws an InterruptedException when the thread is interrupted
queue.put(new ProducedData());
}
} catch (InterruptedException e) {
// the thread has been interrupted: cleanup and exit
producedCount--;
//re-interrupt the thread in case the interrupt flag is needeed higher up
Thread.currentThread().interrupt();
}
System.out.println("Produced " + producedCount + " objects");
}
}
public class Consumer implements Runnable {
private final BlockingQueue<ProducedData> queue;
public Consumer(BlockingQueue<ProducedData> queue) {
this.queue = queue;
}
public void run() {
int consumedCount = 0;
try {
while (true) {
//put throws an InterruptedException when the thread is interrupted
ProducedData data = queue.poll(10, TimeUnit.MILLISECONDS);
// process data
consumedCount++;
}
} catch (InterruptedException e) {
// the thread has been interrupted: cleanup and exit
consumedCount--;
//re-interrupt the thread in case the interrupt flag is needeed higher up
Thread.currentThread().interrupt();
}
System.out.println("Consumed " + consumedCount + " objects");
}
}
public class ProducerConsumerExample {
static class ProducedData {
// empty data object
}
public static void main(String[] args) throws InterruptedException {
BlockingQueue<ProducedData> queue = new ArrayBlockingQueue<ProducedData>(1000);
// choice of queue determines the actual behavior: see various BlockingQueue implementations
Thread producer = new Thread(new Producer(queue));
Thread consumer = new Thread(new Consumer(queue));
producer.start();
consumer.start();
Thread.sleep(1000);
producer.interrupt();
Thread.sleep(10);
consumer.interrupt();
}
}
थ्रेडलोक का उपयोग करना
Java Concurrency में एक उपयोगी टूल ThreadLocal
- यह आपको एक वैरिएबल रखने की अनुमति देता है जो किसी दिए गए थ्रेड के लिए अद्वितीय होगा। इस प्रकार, यदि समान कोड विभिन्न थ्रेड्स में चलता है, तो ये निष्पादन मूल्य साझा नहीं करेंगे, लेकिन इसके बजाय प्रत्येक थ्रेड का अपना चर है जो थ्रेड के लिए स्थानीय है ।
उदाहरण के लिए, यह अक्सर एक सर्वलेट में अनुरोध को संभालने के संदर्भ (जैसे प्राधिकरण जानकारी) को स्थापित करने के लिए उपयोग किया जाता है। आप ऐसा कुछ कर सकते हैं:
private static final ThreadLocal<MyUserContext> contexts = new ThreadLocal<>();
public static MyUserContext getContext() {
return contexts.get(); // get returns the variable unique to this thread
}
public void doGet(...) {
MyUserContext context = magicGetContextFromRequest(request);
contexts.put(context); // save that context to our thread-local - other threads
// making this call don't overwrite ours
try {
// business logic
} finally {
contexts.remove(); // 'ensure' removal of thread-local variable
}
}
अब, हर एक विधि में MyUserContext
पास करने के बजाय, आप इसके बजाय MyServlet.getContext()
उपयोग कर सकते हैं जहाँ आपको इसकी आवश्यकता है। अब निश्चित रूप से, यह एक वैरिएबल को पेश करता है जिसे प्रलेखित करने की आवश्यकता होती है, लेकिन यह थ्रेड-सेफ है, जो इस तरह के अत्यधिक स्कूप्ड वैरिएबल का उपयोग करने के लिए कई डाउनसाइड को समाप्त करता है।
यहाँ महत्वपूर्ण लाभ यह है कि हर धागे का अपना contexts
स्थानीय चर उस contexts
कंटेनर में होता है। जब तक आप इसे एक निर्धारित प्रविष्टि बिंदु से उपयोग करते हैं (जैसे कि प्रत्येक सर्वलेट अपने संदर्भ को बनाए रखने की मांग करता है, या शायद एक सर्वलेट फ़िल्टर जोड़कर) आप इस संदर्भ पर भरोसा कर सकते हैं जब आपको इसकी आवश्यकता हो।
CountDownLatch
एक सिंक्रनाइज़ेशन सहायता जो एक या अधिक थ्रेड्स को प्रतीक्षा करने की अनुमति देती है जब तक कि अन्य थ्रेड्स में किए गए संचालन का एक सेट पूरा नहीं हो जाता।
- एक
CountDownLatch
एक दिए गए गिनती के साथ शुरू होता है। -
countDown()
विधि के इनवोकेशन के कारण करंट काउंट शून्य तक पहुंचने तकcountDown()
तरीके ब्लॉक हो जाते हैं, जिसके बाद सभी वेटिंग थ्रेड जारी होते हैं और वेटिंग के किसी भी बाद के इनवोकेशन तुरंत लौट आते हैं। - यह एक-शॉट वाली घटना है - गिनती को रीसेट नहीं किया जा सकता है। यदि आपको एक संस्करण की आवश्यकता है जो गणना को रीसेट करता है, तो एक
CyclicBarrier
का उपयोग करने पर विचार करें।
प्रमुख विधियाँ:
public void await() throws InterruptedException
जब तक कुंडी शून्य तक गिना नहीं जाता है, तब तक प्रतीक्षा करने के लिए वर्तमान धागे का कारण बनता है, जब तक कि धागा बाधित नहीं होता है।
public void countDown()
यदि गिनती शून्य तक पहुँचती है, तो सभी प्रतीक्षा धागे जारी करते हुए, कुंडी की गिनती को घटाता है।
उदाहरण:
import java.util.concurrent.*;
class DoSomethingInAThread implements Runnable {
CountDownLatch latch;
public DoSomethingInAThread(CountDownLatch latch) {
this.latch = latch;
}
public void run() {
try {
System.out.println("Do some thing");
latch.countDown();
} catch(Exception err) {
err.printStackTrace();
}
}
}
public class CountDownLatchDemo {
public static void main(String[] args) {
try {
int numberOfThreads = 5;
if (args.length < 1) {
System.out.println("Usage: java CountDownLatchDemo numberOfThreads");
return;
}
try {
numberOfThreads = Integer.parseInt(args[0]);
} catch(NumberFormatException ne) {
}
CountDownLatch latch = new CountDownLatch(numberOfThreads);
for (int n = 0; n < numberOfThreads; n++) {
Thread t = new Thread(new DoSomethingInAThread(latch));
t.start();
}
latch.await();
System.out.println("In Main thread after completion of " + numberOfThreads + " threads");
} catch(Exception err) {
err.printStackTrace();
}
}
}
उत्पादन:
java CountDownLatchDemo 5
Do some thing
Do some thing
Do some thing
Do some thing
Do some thing
In Main thread after completion of 5 threads
स्पष्टीकरण:
-
CountDownLatch
को मुख्य धागे में 5 के काउंटर के साथ आरंभीकृत किया गया है - मुख्य धागा
await()
विधि का उपयोग करकेawait()
कर रहा है। -
DoSomethingInAThread
पाँच उदाहरण बनाए गए हैं। प्रत्येक उदाहरण नेcountDown()
विधि के साथ काउंटर कोcountDown()
। - काउंटर शून्य हो जाने के बाद, मुख्य धागा फिर से शुरू होगा
तादात्म्य
जावा में, एक अंतर्निहित भाषा-स्तर लॉकिंग तंत्र है: synchronized
ब्लॉक, जो किसी भी जावा ऑब्जेक्ट को आंतरिक लॉक के रूप में उपयोग कर सकता है (यानी प्रत्येक जावा ऑब्जेक्ट में एक मॉनिटर जुड़ा हो सकता है)।
आंतरिक ताले बयानों के समूहों को परमाणुता प्रदान करते हैं। यह समझने के लिए कि हमारे लिए इसका क्या अर्थ है, आइए एक उदाहरण देखें जहां synchronized
करना उपयोगी है:
private static int t = 0;
private static Object mutex = new Object();
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(400); // The high thread count is for demonstration purposes.
for (int i = 0; i < 100; i++) {
executorService.execute(() -> {
synchronized (mutex) {
t++;
System.out.println(MessageFormat.format("t: {0}", t));
}
});
}
executorService.shutdown();
}
इस स्थिति में, यदि यह synchronized
ब्लॉक के लिए नहीं थे, तो इसमें कई समसामयिक मुद्दे शामिल होंगे। पहला पोस्ट इन्क्रीमेंट ऑपरेटर के साथ होगा (यह अपने आप में परमाणु नहीं है), और दूसरा यह होगा कि अन्य थ्रेड्स की एक मनमानी राशि को संशोधित करने का मौका मिलने के बाद हम टी का मान देख रहे होंगे। हालाँकि, चूंकि हमने एक आंतरिक लॉक का अधिग्रहण किया है, इसलिए यहां कोई दौड़ की स्थिति नहीं होगी और आउटपुट में उनके सामान्य क्रम में 1 से 100 तक नंबर होंगे।
जावा में आंतरिक ताले म्यूटेक्स (यानी आपसी निष्पादन ताले) हैं। म्युचुअल निष्पादन का मतलब है कि यदि एक थ्रेड ने लॉक का अधिग्रहण किया है, तो दूसरा इसे जारी करने के लिए पहले इसे जारी करने के लिए इंतजार करने के लिए मजबूर किया जाएगा। नोट: एक ऑपरेशन जो थ्रेड को प्रतीक्षा (नींद) स्थिति में डाल सकता है, उसे ब्लॉकिंग ऑपरेशन कहा जाता है। इस प्रकार, एक ताला प्राप्त करना एक अवरुद्ध ऑपरेशन है।
जावा में आंतरिक ताले पुनर्व्यवस्थित हैं । इसका मतलब यह है कि यदि कोई थ्रेड पहले से ही मालिक है, तो वह लॉक हासिल करने का प्रयास करता है, यह ब्लॉक नहीं होगा और वह सफलतापूर्वक इसे हासिल कर लेगा। उदाहरण के लिए, निम्न कोड को ब्लॉक नहीं किया जाएगा:
public void bar(){
synchronized(this){
...
}
}
public void foo(){
synchronized(this){
bar();
}
}
synchronized
ब्लॉक के अलावा, वहाँ भी synchronized
तरीके हैं।
कोड के निम्नलिखित ब्लॉक व्यावहारिक रूप से समतुल्य हैं (भले ही बाइटकोड अलग-अलग लगता है):
this
परsynchronized
ब्लॉक:public void foo() { synchronized(this) { doStuff(); } }
synchronized
विधि:public synchronized void foo() { doStuff(); }
इसी तरह static
तरीकों के लिए, यह:
class MyClass {
...
public static void bar() {
synchronized(MyClass.class) {
doSomeOtherStuff();
}
}
}
इसका प्रभाव समान है:
class MyClass {
...
public static synchronized void bar() {
doSomeOtherStuff();
}
}
परमाणु संचालन
परमाणु ऑपरेशन एक ऐसा ऑपरेशन होता है, जिसे "सभी को एक बार" निष्पादित किया जाता है, जो परमाणु संचालन के निष्पादन के दौरान राज्य को देखने या संशोधित करने के किसी भी अवसर के बिना।
आइए एक BAD EXAMPLE पर विचार करें।
private static int t = 0;
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(400); // The high thread count is for demonstration purposes.
for (int i = 0; i < 100; i++) {
executorService.execute(() -> {
t++;
System.out.println(MessageFormat.format("t: {0}", t));
});
}
executorService.shutdown();
}
इस मामले में, दो मुद्दे हैं। पहला मुद्दा यह है कि पोस्ट इंक्रीमेंट ऑपरेटर परमाणु नहीं है । इसमें कई ऑपरेशन शामिल होते हैं: मूल्य प्राप्त करें, मूल्य में 1 जोड़ें, मूल्य सेट करें। इसीलिए यदि हम उदाहरण को चलाते हैं, तो संभावना है कि हम t: 100
को आउटपुट में नहीं देखेंगे - दो सूत्र समवर्ती रूप से मूल्य प्राप्त कर सकते हैं, इसे बढ़ा सकते हैं और इसे सेट कर सकते हैं: मान लें कि टी का मान 10 है, और दो धागे टी बढ़ रहे हैं। दोनों धागे 11 से t का मान निर्धारित करेंगे, क्योंकि दूसरा धागा पहले मान को समाप्त करने से पहले t का मान देखता है।
दूसरा मुद्दा यह है कि हम कैसे टी का अवलोकन कर रहे हैं। जब हम t के मान को प्रिंट कर रहे होते हैं, तो हो सकता है कि इस थ्रेड के इन्क्रीमेंट ऑपरेशन के बाद वैल्यू को पहले ही एक अलग थ्रेड द्वारा बदल दिया जाए।
उन समस्याओं को ठीक करने के लिए, हम java.util.concurrent.atomic.AtomicInteger
उपयोग करेंगे, जिसमें हमारे उपयोग के लिए कई परमाणु संचालन हैं।
private static AtomicInteger t = new AtomicInteger(0);
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(400); // The high thread count is for demonstration purposes.
for (int i = 0; i < 100; i++) {
executorService.execute(() -> {
int currentT = t.incrementAndGet();
System.out.println(MessageFormat.format("t: {0}", currentT));
});
}
executorService.shutdown();
}
incrementAndGet
ऑफ AtomicInteger
विधि परमाणु वृद्धि को AtomicInteger
और नए मूल्य को लौटाती है, इस प्रकार पिछली दौड़ की स्थिति को समाप्त कर देती है। कृपया ध्यान दें कि इस उदाहरण में लाइनें अभी भी क्रम से बाहर हो जाएंगी क्योंकि हम println
कॉल को अनुक्रमित करने का कोई प्रयास नहीं करते हैं और यह इस उदाहरण के दायरे से बाहर है, क्योंकि इसे सिंक्रनाइज़ेशन की आवश्यकता होगी और इस उदाहरण का लक्ष्य यह दिखाना है कि कैसे राज्य से संबंधित दौड़ की स्थितियों को खत्म करने के लिए AtomicInteger
का उपयोग AtomicInteger
।
बुनियादी गतिरोध प्रणाली बनाना
एक गतिरोध तब होता है जब दो प्रतिस्पर्धी क्रियाएं दूसरे के समाप्त होने की प्रतीक्षा करती हैं, और इस प्रकार न तो कभी करता है। जावा में प्रत्येक वस्तु से जुड़ा एक लॉक होता है। एकल ऑब्जेक्ट पर कई थ्रेड्स द्वारा किए गए समवर्ती संशोधन से बचने के लिए हम synchronized
कीवर्ड का उपयोग कर सकते हैं, लेकिन सब कुछ एक लागत पर आता है। गलत तरीके से synchronized
कीवर्ड का इस्तेमाल करने से अटके हुए सिस्टम को डेडलॉक सिस्टम कहा जा सकता है।
विचार करें कि 1 उदाहरण पर 2 थ्रेड काम कर रहे हैं, पहले और दूसरे के रूप में थ्रेड्स को कॉल करें, और हम 2 संसाधनों आर 1 और आर 2 कहते हैं। पहला R1 का अधिग्रहण करता है और इसके पूरा होने के लिए भी R2 की जरूरत है जबकि दूसरा R2 का अधिग्रहण करता है और पूरा होने के लिए R1 की जरूरत है।
इसलिए समय t = 0 पर कहें,
पहले में आर 1 और दूसरे में आर 2 है। अब फर्स्ट R2 का इंतज़ार कर रहा है जबकि दूसरा R1 का इंतज़ार कर रहा है। यह प्रतीक्षा अनिश्चित है और इससे गतिरोध उत्पन्न होता है।
public class Example2 {
public static void main(String[] args) throws InterruptedException {
final DeadLock dl = new DeadLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
dl.methodA();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
dl.method2();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
t1.setName("First");
t2.setName("Second");
t1.start();
t2.start();
}
}
class DeadLock {
Object mLock1 = new Object();
Object mLock2 = new Object();
public void methodA() {
System.out.println("methodA wait for mLock1 " + Thread.currentThread().getName());
synchronized (mLock1) {
System.out.println("methodA mLock1 acquired " + Thread.currentThread().getName());
try {
Thread.sleep(100);
method2();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void method2() throws InterruptedException {
System.out.println("method2 wait for mLock2 " + Thread.currentThread().getName());
synchronized (mLock2) {
System.out.println("method2 mLock2 acquired " + Thread.currentThread().getName());
Thread.sleep(100);
method3();
}
}
public void method3() throws InterruptedException {
System.out.println("method3 mLock1 "+ Thread.currentThread().getName());
synchronized (mLock1) {
System.out.println("method3 mLock1 acquired " + Thread.currentThread().getName());
}
}
}
इस कार्यक्रम का आउटपुट:
methodA wait for mLock1 First
method2 wait for mLock2 Second
method2 mLock2 acquired Second
methodA mLock1 acquired First
method3 mLock1 Second
method2 wait for mLock2 First
विराम लगाना
Thread.sleep
एक निर्दिष्ट अवधि के लिए निष्पादन को निलंबित करने के लिए वर्तमान थ्रेड का कारण बनता है। यह किसी एप्लिकेशन या अन्य अनुप्रयोगों के प्रोसेसर के लिए समय उपलब्ध कराने का एक कुशल साधन है जो कंप्यूटर सिस्टम पर चल रहा हो सकता है। थ्रेड क्लास में दो ओवरलोड sleep
तरीके हैं।
एक जो नींद के समय को मिलीसेकंड के लिए निर्दिष्ट करता है
public static void sleep(long millis) throws InterruptedException
एक जो नैनोसेकंड की नींद के समय को निर्दिष्ट करता है
public static void sleep(long millis, int nanos)
1 सेकंड के लिए निष्पादन रोकना
Thread.sleep(1000);
यह नोट करना महत्वपूर्ण है कि यह ऑपरेटिंग सिस्टम के कर्नेल के शेड्यूलर के लिए एक संकेत है। यह आवश्यक रूप से सटीक नहीं हो सकता है, और कुछ कार्यान्वयन नैनोसेकंड पैरामीटर (संभवतः निकटतम मिलीसेकंड के लिए गोलाई) पर भी विचार नहीं करते हैं।
यह कॉल करने की कोशिश की Thread.sleep
। Thread.sleep
को Thread.sleep
की कोशिश करें / Thread.sleep
और InterruptedException
।
सिंक्रनाइज़ / वाष्पशील का उपयोग करते समय अवरोधों को पढ़ने / लिखने की कल्पना करना
जैसा कि हम जानते हैं कि हमें किसी विधि का निष्पादन करने के लिए या विशेष ब्लॉक करने के लिए synchronized
कीवर्ड का उपयोग करना चाहिए। लेकिन हम में से कुछ को synchronized
और volatile
कीवर्ड का उपयोग करने के एक और महत्वपूर्ण पहलू के बारे में पता नहीं हो सकता है: कोड परमाणु की एक इकाई बनाने के अलावा, यह रीड / राइट बैरियर भी प्रदान करता है । यह क्या है पढ़ना / लिखना बाधा? आइए एक उदाहरण का उपयोग करके इस पर चर्चा करें:
class Counter {
private Integer count = 10;
public synchronized void incrementCount() {
count++;
}
public Integer getCount() {
return count;
}
}
मान लीजिए कि थ्रेड ए कॉल incrementCount()
पहले incrementCount()
फिर एक और थ्रेड बी कॉल getCount()
। इस परिदृश्य में इस बात की कोई गारंटी नहीं है कि बी count
अद्यतन मूल्य को देखेगा। यह अभी भी 10
रूप में count
देख सकता है, यहां तक कि यह भी संभव है कि यह कभी भी count
अद्यतन मूल्य को नहीं देखता है।
इस व्यवहार को समझने के लिए हमें यह समझने की आवश्यकता है कि जावा मेमोरी मॉडल हार्डवेयर आर्किटेक्चर के साथ कैसे एकीकृत होता है। जावा में, प्रत्येक थ्रेड में स्वयं थ्रेड स्टैक होता है। इस स्टैक में शामिल हैं: मेथड कॉल स्टैक और लोकल वैरिएबल उस थ्रेड में निर्मित। मल्टी कोर सिस्टम में, यह काफी संभव है कि दो धागे अलग-अलग कोर में समवर्ती चल रहे हैं। ऐसे परिदृश्य में यह संभव है कि किसी थ्रेड के स्टैक का हिस्सा कोर के रजिस्टर / कैश के अंदर हो। यदि किसी थ्रेड के अंदर, किसी ऑब्जेक्ट को synchronized
(या volatile
) कीवर्ड का उपयोग करके एक्सेस किया जाता है, तो synchronized
ब्लॉक के बाद जो थ्रेड सिंक करता है, वह मुख्य मेमोरी के साथ उस वेरिएबल की लोकल कॉपी है। यह एक रीड / राइट बैरियर बनाता है और यह सुनिश्चित करता है कि थ्रेड उस ऑब्जेक्ट का नवीनतम मान देख रहा है।
लेकिन हमारे मामले में, के बाद से धागा सिंक्रनाइज़ पहुँच इस्तेमाल नहीं किया करने के लिए count
, इसके बारे में बात कर रहा हो सकता है मूल्य count
रजिस्टर में संग्रहीत और कभी नहीं देख सकते हैं धागा ए से अद्यतन सुनिश्चित करें कि बी गिनती के नवीनतम मूल्य देखता है हम बनाने की जरूरत है बनाने के getCount()
रूप में अच्छी तरह से सिंक्रनाइज़।
public synchronized Integer getCount() {
return count;
}
अब जब थ्रेड ए को अपडेटिंग count
साथ किया जाता है तो यह Counter
इंस्टेंस को अनलॉक करता है, उसी समय राइट बैरियर बनाता है और उस ब्लॉक के अंदर किए गए सभी बदलावों को मुख्य मेमोरी में फ्लश करता है। इसी तरह जब थ्रेड बी Counter
के एक ही उदाहरण पर लॉक प्राप्त करता है, तो यह रीड बैरियर में प्रवेश करता है और मुख्य मेमोरी से count
मूल्य पढ़ता है और इन अपडेट को देखता है।
समान दृश्यता प्रभाव volatile
पढ़ने / लिखने के लिए भी जाता है। volatile
को लिखने से पहले अद्यतन किए गए सभी चर मुख्य मेमोरी में प्रवाहित किए जाएंगे और सभी volatile
चर पढ़ने के बाद मुख्य मेमोरी से होंगे।
एक java.lang.Thread उदाहरण बनाना
जावा में एक धागा बनाने के लिए दो मुख्य दृष्टिकोण हैं। संक्षेप में, एक धागा बनाना उतना ही आसान है जितना उस कोड को लिखना जो उसमें निष्पादित किया जाएगा। आप जिस कोड को परिभाषित करते हैं, उसमें दो दृष्टिकोण भिन्न होते हैं।
जावा में, एक थ्रेड को किसी ऑब्जेक्ट द्वारा दर्शाया जाता है - java.lang.Thread या उसके उपवर्ग का एक उदाहरण। तो पहला तरीका यह है कि उपवर्ग बनाएं और रन () विधि को ओवरराइड करें।
नोट : मैं थ्रेड का उपयोग java.lang.hread क्लास और थ्रेड को संदर्भित करने के लिए करूंगा ताकि थ्रेड्स की तार्किक अवधारणा का उल्लेख किया जा सके।
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Thread running!");
}
}
}
अब चूंकि हम पहले ही कोड को निष्पादित करने के लिए परिभाषित कर चुके हैं, इसलिए थ्रेड को इस प्रकार बनाया जा सकता है:
MyThread t = new MyThread();
थ्रेड क्लास में एक स्ट्रिंगर को स्वीकार करने वाला एक कंस्ट्रक्टर भी होता है, जिसे थ्रेड के नाम के रूप में उपयोग किया जाएगा। बहु थ्रेड प्रोग्राम डीबग करते समय यह कण उपयोगी हो सकता है।
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Thread running! ");
}
}
}
MyThread t = new MyThread("Greeting Producer");
दूसरा तरीका java.lang.Runnable का उपयोग करके कोड को परिभाषित करना है और इसकी एकमात्र विधि रन () है । थ्रेड क्लास आपको एक अलग थ्रेड में उस विधि को निष्पादित करने की अनुमति देता है। इसे प्राप्त करने के लिए, रननेबल इंटरफ़ेस का एक उदाहरण स्वीकार करने वाले एक निर्माता का उपयोग करके धागा बनाएं।
Thread t = new Thread(aRunnable);
यह बहुत शक्तिशाली हो सकता है जब लैम्ब्डा या विधियों के संदर्भों के साथ संयुक्त हो (केवल जावा 8):
Thread t = new Thread(operator::hardWork);
आप थ्रेड का नाम भी निर्दिष्ट कर सकते हैं।
Thread t = new Thread(operator::hardWork, "Pi operator");
व्यावहारिक रूप से, आप चिंताओं के बिना दोनों दृष्टिकोणों का उपयोग कर सकते हैं। हालांकि सामान्य ज्ञान बाद का उपयोग करने के लिए कहता है।
चार उल्लेखित निर्माणकर्ताओं में से प्रत्येक के लिए, पहले पैरामीटर के रूप में java.lang.ThreadGroup का एक उदाहरण स्वीकार करने वाला एक विकल्प भी है।
ThreadGroup tg = new ThreadGroup("Operators");
Thread t = new Thread(tg, operator::hardWork, "PI operator");
थ्रेडग्रुप धागे के एक सेट का प्रतिनिधित्व करता है। आप केवल एक जोड़ सकते हैं थ्रेड एक को ThreadGroup एक का उपयोग कर थ्रेड के निर्माता। ThreadGroup तो अपने सभी का प्रबंधन करने के लिए किया जा सकता थ्रेड रों एक साथ, साथ ही धागा अपने से जानकारी हासिल कर सकते हैं ThreadGroup ।
तो संक्षिप्त करने के लिए, थ्रेड को इन सार्वजनिक निर्माणकर्ताओं में से एक के साथ बनाया जा सकता है:
Thread()
Thread(String name)
Thread(Runnable target)
Thread(Runnable target, String name)
Thread(ThreadGroup group, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
आखिरी हमें नए धागे के लिए वांछित स्टैक आकार को परिभाषित करने की अनुमति देता है।
कई थ्रेड को समान गुणों से या समान पैटर्न से बनाते और कॉन्फ़िगर करते समय अक्सर कोड पठनीयता ग्रस्त हो जाती है। तभी java.util.concurrent.ThreadFactory का उपयोग किया जा सकता है। यह इंटरफ़ेस आपको फैक्ट्री पैटर्न के माध्यम से थ्रेड बनाने की प्रक्रिया और इसकी एकमात्र विधि newThread (Runnable) की अनुमति देता है ।
class WorkerFactory implements ThreadFactory {
private int id = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "Worker " + id++);
}
}
थ्रेड इंटरप्टियन / स्टॉपिंग थ्रेड्स
प्रत्येक जावा थ्रेड में एक बाधा झंडा होता है, जो शुरू में झूठा होता है। एक धागे को बाधित करना, वास्तव में उस झंडे को सच करने से ज्यादा कुछ नहीं है। उस धागे पर चलने वाला कोड अवसर पर ध्वज की जांच कर सकता है और उस पर कार्रवाई कर सकता है। कोड इसे पूरी तरह से अनदेखा भी कर सकता है। लेकिन प्रत्येक थ्रेड में ऐसा ध्वज क्यों होगा? सब के बाद, एक धागे पर एक बूलीयन झंडा होने से कुछ ऐसा होता है जिसे हम बस खुद को व्यवस्थित कर सकते हैं, अगर और जब हमें इसकी आवश्यकता होती है। खैर, ऐसे तरीके हैं जो एक विशेष तरीके से व्यवहार करते हैं जब वे जिस धागे पर चल रहे हैं वह बाधित होता है। इन तरीकों को ब्लॉकिंग मेथड्स कहा जाता है। ये ऐसी विधियाँ हैं जो धागा को WAITING या TIMED_WAITING स्थिति में डालती हैं। जब कोई थ्रेड इस स्थिति में होता है, तो उसे बाधित करते हुए, इंटरप्टेड एक्ससेप्शन को बाधित थ्रेड पर फेंकने के लिए होगा, बजाय इसके कि इंटरप्ट फ्लैग को सही सेट किए, और थ्रेड फिर से RUNNABLE हो जाए। एक अवरोधन विधि को लागू करने वाले कोड को InterruptedException से निपटने के लिए मजबूर किया जाता है, क्योंकि यह एक जाँच अपवाद है। तो, और इसलिए इसका नाम, एक अवरोध एक WAIT को बाधित करने का प्रभाव हो सकता है, प्रभावी रूप से इसे समाप्त कर सकता है। ध्यान दें कि वे सभी विधियाँ जो किसी तरह प्रतीक्षा नहीं कर रही हैं (जैसे कि IO को अवरुद्ध करना) उस तरह से रुकावट का जवाब देती हैं, क्योंकि वे धागे को प्रतीक्षा की स्थिति में नहीं रखती हैं। अंत में एक थ्रेड जिसमें इसका इंटरप्ट फ्लैग सेट होता है, जो एक ब्लॉकिंग विधि में प्रवेश करता है (यानी एक प्रतीक्षा अवस्था में आने की कोशिश करता है), तुरंत एक इंटरप्टेड एक्ससेप्शन फेंक देगा और इंटरप्ट फ्लैग को क्लीयर कर दिया जाएगा।
इन यांत्रिकी के अलावा, जावा रुकावट के लिए कोई विशेष अर्थ अर्थ प्रदान नहीं करता है। कोड किसी भी तरह से एक बाधा की व्याख्या करने के लिए स्वतंत्र है जो इसे पसंद करता है। लेकिन सबसे अधिक बार रुकावट का उपयोग एक थ्रेड को इंगित करने के लिए किया जाता है, जिसे इसकी शुरुआती सुविधा पर चलना बंद कर देना चाहिए। लेकिन, जैसा कि ऊपर से स्पष्ट होना चाहिए, यह उस थ्रेड पर कोड के लिए है कि उस रुकावट पर प्रतिक्रिया करने के लिए उचित रूप से चलने से रोकने के लिए। एक धागा रोकना एक सहयोग है। जब एक थ्रेड बाधित होता है तो इसका रनिंग कोड स्टैकट्रेस में कई स्तर गहरा हो सकता है। अधिकांश कोड ब्लॉकिंग विधि को नहीं कहते हैं, और थ्रेड को रोकने में देरी न करने के लिए समय पर पर्याप्त रूप से समाप्त हो जाते हैं। कोड जिसे ज्यादातर व्यवधान के प्रति उत्तरदायी होने के साथ संबंधित होना चाहिए, वह कोड है जो लूप हैंडलिंग कार्यों में है जब तक कि कोई भी नहीं बचा है, या जब तक कोई झंडा सेट नहीं होता है तब तक वह उस लूप को रोकने के लिए सिग्नल दे रहा है। लूप्स जो संभवतः अनंत कार्यों को संभालते हैं (यानी वे सिद्धांत रूप में चलते रहते हैं) लूप से बाहर निकलने के लिए बीच में झंडे को जांचना चाहिए। परिमित छोरों के लिए शब्दार्थ यह निर्धारित कर सकता है कि सभी कार्य समाप्त होने से पहले समाप्त होने चाहिए, या कुछ कार्यों को छोड़ना उचित होगा। अवरोधक कॉल करने वाले कोड को InterruptedException से निपटने के लिए बाध्य किया जाएगा। यदि सभी संभव रूप से संभव हो, तो यह केवल InterruptedException को प्रचारित कर सकता है और इसे फेंकने की घोषणा कर सकता है। जैसे कि यह अपने कॉल करने वालों के संबंध में एक अवरुद्ध पद्धति बन जाता है। यदि यह अपवाद का प्रचार नहीं कर सकता है, तो इसे कम से कम बाधित ध्वज को सेट करना चाहिए, इसलिए स्टैक को उच्चतर करने से भी पता चलता है कि धागा बाधित हो गया था। कुछ मामलों में विधि को बाधित किए बिना प्रतीक्षा जारी रखने की आवश्यकता होती है, उस स्थिति में जब तक यह प्रतीक्षा नहीं की जाती है तब तक बाधित ध्वज को सेट करने में देरी करनी चाहिए, इसमें स्थानीय चर सेट करना शामिल हो सकता है, जिसे विधि से बाहर निकलने से पहले जांचना होता है फिर इसके धागे को बाधित करें।
उदाहरण :
कोड का उदाहरण जो रुकावट पर कार्यों को संभालना बंद कर देता है
class TaskHandler implements Runnable {
private final BlockingQueue<Task> queue;
TaskHandler(BlockingQueue<Task> queue) {
this.queue = queue;
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) { // check for interrupt flag, exit loop when interrupted
try {
Task task = queue.take(); // blocking call, responsive to interruption
handle(task);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // cannot throw InterruptedException (due to Runnable interface restriction) so indicating interruption by setting the flag
}
}
}
private void handle(Task task) {
// actual handling
}
}
कोड का उदाहरण जो पूरी तरह से किए जाने तक बाधा झंडा लगाने में देरी करता है:
class MustFinishHandler implements Runnable {
private final BlockingQueue<Task> queue;
MustFinishHandler(BlockingQueue<Task> queue) {
this.queue = queue;
}
@Override
public void run() {
boolean shouldInterrupt = false;
while (true) {
try {
Task task = queue.take();
if (task.isEndOfTasks()) {
if (shouldInterrupt) {
Thread.currentThread().interrupt();
}
return;
}
handle(task);
} catch (InterruptedException e) {
shouldInterrupt = true; // must finish, remember to set interrupt flag when we're done
}
}
}
private void handle(Task task) {
// actual handling
}
}
कोड का उदाहरण जिसमें कार्यों की एक निश्चित सूची है लेकिन बाधित होने पर जल्दी छोड़ सकते हैं
class GetAsFarAsPossible implements Runnable {
private final List<Task> tasks = new ArrayList<>();
@Override
public void run() {
for (Task task : tasks) {
if (Thread.currentThread().isInterrupted()) {
return;
}
handle(task);
}
}
private void handle(Task task) {
// actual handling
}
}
साझा वैश्विक कतार के साथ कई निर्माता / उपभोक्ता उदाहरण
नीचे दिए गए कोड में कई निर्माता / उपभोक्ता कार्यक्रम दिखाए गए हैं। निर्माता और उपभोक्ता दोनों धागे समान वैश्विक कतार साझा करते हैं।
import java.util.concurrent.*;
import java.util.Random;
public class ProducerConsumerWithES {
public static void main(String args[]) {
BlockingQueue<Integer> sharedQueue = new LinkedBlockingQueue<Integer>();
ExecutorService pes = Executors.newFixedThreadPool(2);
ExecutorService ces = Executors.newFixedThreadPool(2);
pes.submit(new Producer(sharedQueue, 1));
pes.submit(new Producer(sharedQueue, 2));
ces.submit(new Consumer(sharedQueue, 1));
ces.submit(new Consumer(sharedQueue, 2));
pes.shutdown();
ces.shutdown();
}
}
/* Different producers produces a stream of integers continuously to a shared queue,
which is shared between all Producers and consumers */
class Producer implements Runnable {
private final BlockingQueue<Integer> sharedQueue;
private int threadNo;
private Random random = new Random();
public Producer(BlockingQueue<Integer> sharedQueue,int threadNo) {
this.threadNo = threadNo;
this.sharedQueue = sharedQueue;
}
@Override
public void run() {
// Producer produces a continuous stream of numbers for every 200 milli seconds
while (true) {
try {
int number = random.nextInt(1000);
System.out.println("Produced:" + number + ":by thread:"+ threadNo);
sharedQueue.put(number);
Thread.sleep(200);
} catch (Exception err) {
err.printStackTrace();
}
}
}
}
/* Different consumers consume data from shared queue, which is shared by both producer and consumer threads */
class Consumer implements Runnable {
private final BlockingQueue<Integer> sharedQueue;
private int threadNo;
public Consumer (BlockingQueue<Integer> sharedQueue,int threadNo) {
this.sharedQueue = sharedQueue;
this.threadNo = threadNo;
}
@Override
public void run() {
// Consumer consumes numbers generated from Producer threads continuously
while(true){
try {
int num = sharedQueue.take();
System.out.println("Consumed: "+ num + ":by thread:"+threadNo);
} catch (Exception err) {
err.printStackTrace();
}
}
}
}
उत्पादन:
Produced:69:by thread:2
Produced:553:by thread:1
Consumed: 69:by thread:1
Consumed: 553:by thread:2
Produced:41:by thread:2
Produced:796:by thread:1
Consumed: 41:by thread:1
Consumed: 796:by thread:2
Produced:728:by thread:2
Consumed: 728:by thread:1
और इसी तरह ................
स्पष्टीकरण:
-
sharedQueue
, जो एकLinkedBlockingQueue
है, सभी निर्माता और उपभोक्ता थ्रेड्स के बीच साझा किया जाता है। - निर्माता धागे प्रत्येक 200 मिली सेकंड के लिए लगातार एक पूर्णांक का उत्पादन करता है और इसे
sharedQueue
-
Consumer
थ्रेडsharedQueue
लगातार पूर्णांक की खपत करता है। - यह कार्यक्रम स्पष्ट रूप से
synchronized
याLock
निर्माण के साथ लागू किया गया है। इसे प्राप्त करने के लिए ब्लॉकिंग क्यू की कुंजी है।
ब्लॉकिंग क्यू कार्यान्वयन को मुख्य रूप से निर्माता-उपभोक्ता कतारों के लिए उपयोग करने के लिए डिज़ाइन किया गया है।
BlockQQueue कार्यान्वयन थ्रेड-सुरक्षित हैं। सभी कतारबद्ध तरीके आंतरिक लॉक या संगामिति नियंत्रण के अन्य रूपों का उपयोग करके अपने प्रभावों को प्राप्त करते हैं।
अनन्य लेखन / समवर्ती पढ़ने का उपयोग
कभी-कभी समान "डेटा" लिखने और पढ़ने के लिए एक प्रक्रिया के लिए इसकी आवश्यकता होती है।
ReadWriteLock
इंटरफ़ेस, और इसका ReentrantReadWriteLock
कार्यान्वयन एक एक्सेस पैटर्न के लिए अनुमति देता है जिसे इस प्रकार वर्णित किया जा सकता है:
- डेटा के समवर्ती पाठकों की कोई भी संख्या हो सकती है। यदि कम से कम एक रीडर एक्सेस दी जाती है, तो कोई भी लेखक एक्सेस संभव नहीं है।
- डेटा के लिए एक एकल लेखक हो सकता है। यदि कोई लेखक पहुंच प्रदान करता है, तो कोई भी पाठक डेटा तक नहीं पहुंच सकता है।
एक कार्यान्वयन की तरह लग सकता है:
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Sample {
// Our lock. The constructor allows a "fairness" setting, which guarantees the chronology of lock attributions.
protected static final ReadWriteLock RW_LOCK = new ReentrantReadWriteLock();
// This is a typical data that needs to be protected for concurrent access
protected static int data = 0;
/** This will write to the data, in an exclusive access */
public static void writeToData() {
RW_LOCK.writeLock().lock();
try {
data++;
} finally {
RW_LOCK.writeLock().unlock();
}
}
public static int readData() {
RW_LOCK.readLock().lock();
try {
return data;
} finally {
RW_LOCK.readLock().unlock();
}
}
}
नोट 1 : इस सटीक उपयोग के मामले में AtomicInteger
का उपयोग करके एक क्लीनर समाधान है, लेकिन यहां जो वर्णन किया गया है वह एक एक्सेस पैटर्न है, जो इस तथ्य की परवाह किए बिना काम करता है कि यहां डेटा एक पूर्णांक है जो कि परमाणु संस्करण के रूप में है।
नोट 2 : पढ़ने वाले हिस्से पर ताला वास्तव में आवश्यक है, हालांकि यह आकस्मिक पाठक के लिए ऐसा नहीं लग सकता है। वास्तव में, यदि आप रीडर की ओर से लॉक नहीं करते हैं, तो किसी भी संख्या में गलत हो सकता है, जिनमें से:
- आदिम मूल्यों के लेखन की सभी जेवीएम पर परमाणु होने की गारंटी नहीं है, इसलिए पाठक उदाहरण देख सकते हैं कि 64 बिट्स में से केवल 32 बिट्स लिखते हैं अगर
data
64 बिट लंबा प्रकार था - एक थ्रेड से लेखन की दृश्यता जो इसे प्रदर्शन नहीं करती थी, जेवीएम द्वारा गारंटी दी जाती है, जब हम राइट्स और रीड्स के बीच संबंध स्थापित करते हैं। यह संबंध तब स्थापित होता है जब पाठक और लेखक दोनों अपने-अपने ताले का उपयोग करते हैं, लेकिन अन्यथा नहीं
यदि उच्च प्रदर्शन की आवश्यकता होती है, तो एक निश्चित प्रकार के उपयोग के तहत, एक तेजी से लॉक प्रकार उपलब्ध है, जिसे StampedLock
कहा जाता है, अन्य चीजों के बीच एक आशावादी लॉक मोड को लागू करता है। यह लॉक ReadWriteLock
से बहुत अलग तरीके से काम करता है, और यह नमूना ट्रांस्प्लांट नहीं है।
चलने योग्य वस्तु
Runnable
इंटरफ़ेस एक एकल विधि, run()
को परिभाषित करता है, जिसका अर्थ है थ्रेड में निष्पादित कोड।
Runnable
ऑब्जेक्ट Thread
कंस्ट्रक्टर को पास किया जाता है। और थ्रेड की start()
विधि कहा जाता है।
उदाहरण
public class HelloRunnable implements Runnable {
@Override
public void run() {
System.out.println("Hello from a thread");
}
public static void main(String[] args) {
new Thread(new HelloRunnable()).start();
}
}
Java8 में उदाहरण:
public static void main(String[] args) {
Runnable r = () -> System.out.println("Hello world");
new Thread(r).start();
}
रननेबल बनाम थ्रेड उपवर्ग
एक Runnable
ऑब्जेक्ट रोजगार अधिक सामान्य है, क्योंकि Runnable
ऑब्जेक्ट Thread
अलावा किसी वर्ग को उपवर्ग कर सकता है।
Thread
उपवर्ग सरल अनुप्रयोगों में उपयोग करना आसान है, लेकिन इस तथ्य से सीमित है कि आपका कार्य वर्ग Thread
वंशज होना चाहिए।
एक Runnable
ऑब्जेक्ट उच्च-स्तरीय थ्रेड प्रबंधन API पर लागू होता है।
सिकंदरा
एक सेमाफोर एक उच्च-स्तरीय सिंक्रनाइज़र है जो परमिट के एक सेट को बनाए रखता है जिसे थ्रेड द्वारा अधिग्रहित और जारी किया जा सकता है। एक सेमीफोर को परमिट के एक काउंटर के रूप में कल्पना की जा सकती है जो थ्रेड को प्राप्त करने पर थ्रेडेड हो जाता है और थ्रेड रिलीज होने पर बढ़ जाता है। यदि परमिट की राशि 0
जब एक धागा प्राप्त करने का प्रयास करता है, तो धागा तब तक ब्लॉक करेगा जब तक कि परमिट उपलब्ध नहीं किया जाता है (या जब तक कि थ्रेड बाधित नहीं होता है)।
एक सेमाफोर के रूप में आरंभ किया जाता है:
Semaphore semaphore = new Semaphore(1); // The int value being the number of permits
सेमफोर कंस्ट्रक्टर निष्पक्षता के लिए एक अतिरिक्त बूलियन पैरामीटर स्वीकार करता है। जब गलत सेट किया जाता है, तो यह वर्ग उस क्रम के बारे में कोई गारंटी नहीं देता है जिसमें धागे परमिट हासिल करते हैं। जब निष्पक्षता सही हो जाती है, तो सेमाफोर गारंटी देता है कि किसी भी तरीके को प्राप्त करने के तरीकों को लागू करने के लिए थ्रेड्स का चयन उस क्रम में परमिट प्राप्त करने के लिए किया जाता है, जिसमें उन तरीकों का इनवोकेशन प्रोसेस किया गया था। यह निम्नलिखित तरीके से घोषित किया गया है:
Semaphore semaphore = new Semaphore(1, true);
अब आइए, javadocs से एक उदाहरण देखें, जहाँ सेमीफ़ोर का उपयोग वस्तुओं के पूल तक पहुँच को नियंत्रित करने के लिए किया जाता है। इस उदाहरण में एक सेमाफोर का उपयोग अवरुद्ध कार्यक्षमता प्रदान करने के लिए किया जाता है ताकि यह सुनिश्चित किया जा सके कि getItem()
जाने पर हमेशा प्राप्त होने वाली getItem()
।
class Pool {
/*
* Note that this DOES NOT bound the amount that may be released!
* This is only a starting value for the Semaphore and has no other
* significant meaning UNLESS you enforce this inside of the
* getNextAvailableItem() and markAsUnused() methods
*/
private static final int MAX_AVAILABLE = 100;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
/**
* Obtains the next available item and reduces the permit count by 1.
* If there are no items available, block.
*/
public Object getItem() throws InterruptedException {
available.acquire();
return getNextAvailableItem();
}
/**
* Puts the item into the pool and add 1 permit.
*/
public void putItem(Object x) {
if (markAsUnused(x))
available.release();
}
private Object getNextAvailableItem() {
// Implementation
}
private boolean markAsUnused(Object o) {
// Implementation
}
}
थ्रेडपूल का उपयोग करके दो `int` सरणियाँ जोड़ें
थ्रेडपूल में कार्यों की एक कतार होती है, जिनमें से प्रत्येक को इन थ्रेड्स पर निष्पादित किया जाएगा।
निम्न उदाहरण से पता चलता है कि कैसे दो जोड़ने के लिए int
एक ThreadPool का उपयोग कर सरणियों।
int[] firstArray = { 2, 4, 6, 8 };
int[] secondArray = { 1, 3, 5, 7 };
int[] result = { 0, 0, 0, 0 };
ExecutorService pool = Executors.newCachedThreadPool();
// Setup the ThreadPool:
// for each element in the array, submit a worker to the pool that adds elements
for (int i = 0; i < result.length; i++) {
final int worker = i;
pool.submit(() -> result[worker] = firstArray[worker] + secondArray[worker] );
}
// Wait for all Workers to finish:
try {
// execute all submitted tasks
pool.shutdown();
// waits until all workers finish, or the timeout ends
pool.awaitTermination(12, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
pool.shutdownNow(); //kill thread
}
System.out.println(Arrays.toString(result));
टिप्पणियाँ:
यह उदाहरण विशुद्ध रूप से सचित्र है। व्यवहार में, इस छोटे से कार्य के लिए थ्रेड्स का उपयोग करके कोई स्पीडअप नहीं होगा। एक मंदी की संभावना है, क्योंकि कार्य निर्माण और समय-निर्धारण के ओवरहेड्स एक कार्य को चलाने के लिए लगने वाले समय को बदल देंगे।
यदि आप जावा 7 और उससे पहले का उपयोग कर रहे हैं, तो आप कार्यों को कार्यान्वित करने के लिए लैम्ब्डा के बजाय अनाम कक्षाओं का उपयोग करेंगे।
सिस्टम थ्रेड को छोड़कर आपके प्रोग्राम द्वारा शुरू किए गए सभी थ्रेड्स की स्थिति प्राप्त करें
सांकेतिक टुकड़ा:
import java.util.Set;
public class ThreadStatus {
public static void main(String args[]) throws Exception {
for (int i = 0; i < 5; i++){
Thread t = new Thread(new MyThread());
t.setName("MyThread:" + i);
t.start();
}
int threadCount = 0;
Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
for (Thread t : threadSet) {
if (t.getThreadGroup() == Thread.currentThread().getThreadGroup()) {
System.out.println("Thread :" + t + ":" + "state:" + t.getState());
++threadCount;
}
}
System.out.println("Thread count started by Main thread:" + threadCount);
}
}
class MyThread implements Runnable {
public void run() {
try {
Thread.sleep(2000);
} catch(Exception err) {
err.printStackTrace();
}
}
}
आउटपुट:
Thread :Thread[MyThread:1,5,main]:state:TIMED_WAITING
Thread :Thread[MyThread:3,5,main]:state:TIMED_WAITING
Thread :Thread[main,5,main]:state:RUNNABLE
Thread :Thread[MyThread:4,5,main]:state:TIMED_WAITING
Thread :Thread[MyThread:0,5,main]:state:TIMED_WAITING
Thread :Thread[MyThread:2,5,main]:state:TIMED_WAITING
Thread count started by Main thread:6
स्पष्टीकरण:
Thread.getAllStackTraces().keySet()
सभी रिटर्न Thread
आवेदन धागे और सिस्टम धागे भी शामिल है। आप केवल धागे की स्थिति में रुचि रखते हैं, अपने आवेदन के द्वारा, पुनरावृति शुरू कर दिया Thread
सेट अपने मुख्य कार्यक्रम धागा के खिलाफ एक विशेष सूत्र के थ्रेड समूह की जाँच करके।
उपरोक्त थ्रेडग्रुप स्थिति की अनुपस्थिति में, प्रोग्राम सिस्टम थ्रेड्स के नीचे की स्थिति देता है:
Reference Handler
Signal Dispatcher
Attach Listener
Finalizer
कॉल करने योग्य और भविष्य
जबकि Runnable
एक अलग थ्रेड में निष्पादित किए जाने वाले कोड को रैप करने का साधन प्रदान करता है, लेकिन इसमें एक सीमा है कि यह निष्पादन से कोई परिणाम नहीं दे सकता है। एक Runnable
के निष्पादन से कुछ वापसी मूल्य प्राप्त करने का एकमात्र तरीका है कि परिणाम को Runnable
बाहर एक दायरे में सुलभ चर को असाइन किया जाए।
Callable
के लिए एक सहकर्मी के रूप में जावा 5 में पेश किया गया था Runnable
। Callable
मूलतः एक ही छोड़कर यह एक है call
के बजाय विधि run
। call
विधि में परिणाम वापस करने की अतिरिक्त क्षमता है और चेक किए गए अपवादों को फेंकने की भी अनुमति है।
एक कॉल करने योग्य कार्य सबमिशन से परिणाम भविष्य के माध्यम से टैप करने के लिए उपलब्ध है
Future
को एक प्रकार का कंटेनर माना जा सकता है जो Callable
गणना का परिणाम देता है। कॉल करने योग्य की गणना दूसरे धागे में हो सकती है, और Future
के परिणाम को टैप करने का कोई भी प्रयास ब्लॉक होगा और उपलब्ध होने के बाद ही परिणाम लौटाएगा।
कॉल करने योग्य इंटरफ़ेस
public interface Callable<V> {
V call() throws Exception;
}
भविष्य
interface Future<V> {
V get();
V get(long timeout, TimeUnit unit);
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
}
कॉल करने योग्य और भविष्य के उदाहरण का उपयोग करना:
public static void main(String[] args) throws Exception {
ExecutorService es = Executors.newSingleThreadExecutor();
System.out.println("Time At Task Submission : " + new Date());
Future<String> result = es.submit(new ComplexCalculator());
// the call to Future.get() blocks until the result is available.So we are in for about a 10 sec wait now
System.out.println("Result of Complex Calculation is : " + result.get());
System.out.println("Time At the Point of Printing the Result : " + new Date());
}
हमारी कॉलेबल जो लंबी गणना करती है
public class ComplexCalculator implements Callable<String> {
@Override
public String call() throws Exception {
// just sleep for 10 secs to simulate a lengthy computation
Thread.sleep(10000);
System.out.println("Result after a lengthy 10sec calculation");
return "Complex Result"; // the result
}
}
उत्पादन
Time At Task Submission : Thu Aug 04 15:05:15 EDT 2016
Result after a lengthy 10sec calculation
Result of Complex Calculation is : Complex Result
Time At the Point of Printing the Result : Thu Aug 04 15:05:25 EDT 2016
भविष्य में अन्य संचालन की अनुमति है
जबकि get()
वास्तविक परिणाम को निकालने का तरीका है
-
get(long timeout, TimeUnit unit)
वर्तमान थ्रेड के दौरान अधिकतम समय अवधि को परिभाषित करता हैget(long timeout, TimeUnit unit)
परिणामस्वरूप प्रतीक्षा होगी; - कार्य कॉल
cancel(mayInterruptIfRunning)
करने के लिए रद्द करेंcancel(mayInterruptIfRunning)
। फ्लैगmayInterrupt
इंगित करता है कि कार्य बाधित होना चाहिए अगर यह शुरू किया गया था और अभी चल रहा है; - यह जांचने के लिए कि क्या कार्य पूरा हो गया है या
isDone()
कॉल करके समाप्त हो गया है; - यह जांचने के लिए कि क्या लंबा कार्य रद्द किया गया था रद्द
isCancelled()
।
सिंक्रोनाइज़ेशन एड्स के रूप में ताले
जावा 5 के समवर्ती पैकेज परिचय से पहले थ्रेडिंग निम्न स्तर की थी। इस पैकेज की शुरूआत में कई उच्च स्तरीय समवर्ती प्रोग्रामिंग एड्स / निर्माण प्रदान किए गए।
ताले थ्रेड सिंक्रोनाइज़ेशन तंत्र हैं जो अनिवार्य रूप से सिंक्रनाइज़ किए गए ब्लॉक या कुंजी शब्दों के समान उद्देश्य को पूरा करते हैं।
आंतरिक लॉकिंग
int count = 0; // shared among multiple threads
public void doSomething() {
synchronized(this) {
++count; // a non-atomic operation
}
}
ताले का उपयोग कर सिंक्रनाइज़ेशन
int count = 0; // shared among multiple threads
Lock lockObj = new ReentrantLock();
public void doSomething() {
try {
lockObj.lock();
++count; // a non-atomic operation
} finally {
lockObj.unlock(); // sure to release the lock without fail
}
}
ताले में भी कार्यक्षमता उपलब्ध है जो आंतरिक लॉकिंग की पेशकश नहीं करता है, जैसे लॉकिंग लेकिन रुकावट के लिए उत्तरदायी या लॉक करने की कोशिश कर रहा है, और ब्लॉक करने में असमर्थ है।
लॉकिंग, रुकावट के लिए उत्तरदायी
class Locky {
int count = 0; // shared among multiple threads
Lock lockObj = new ReentrantLock();
public void doSomething() {
try {
try {
lockObj.lockInterruptibly();
++count; // a non-atomic operation
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // stopping
}
} finally {
if (!Thread.currentThread().isInterrupted()) {
lockObj.unlock(); // sure to release the lock without fail
}
}
}
}
लॉक करने में सक्षम होने पर ही कुछ करें
public class Locky2 {
int count = 0; // shared among multiple threads
Lock lockObj = new ReentrantLock();
public void doSomething() {
boolean locked = lockObj.tryLock(); // returns true upon successful lock
if (locked) {
try {
++count; // a non-atomic operation
} finally {
lockObj.unlock(); // sure to release the lock without fail
}
}
}
}
लॉक के कई वेरिएंट उपलब्ध हैं। अधिक जानकारी के लिए यहां एपी डॉक्स देखें