JVM को यह मानने की अनुमति है कि pizzaArrivedलूप के दौरान अन्य थ्रेड्स वेरिएबल को नहीं बदलते हैं । दूसरे शब्दों में, यह pizzaArrived == falseलूप के बाहर परीक्षण को फहरा सकता है, इसको अनुकूलित करते हुए:
while (pizzaArrived == false) {}
इस मामले में:
if (pizzaArrived == false) while (true) {}
जो एक अनंत लूप है।
यह सुनिश्चित करने के लिए कि एक थ्रेड द्वारा किए गए परिवर्तन अन्य थ्रेड्स के लिए दिखाई देते हैं, आपको थ्रेड्स के बीच हमेशा कुछ सिंक्रनाइज़ेशन जोड़ना होगा । ऐसा करने का सबसे सरल तरीका साझा चर बनाना है volatile:
volatile boolean pizzaArrived = false;
एक वैरिएबल की volatileगारंटी देता है कि अलग-अलग धागे एक-दूसरे के बदलावों के प्रभावों को देखेंगे। यह JVM pizzaArrivedको लूप के बाहर परीक्षण को रोकने या परीक्षण को रोकने से रोकता है । इसके बजाय, उसे हर बार वास्तविक चर का मूल्य पढ़ना चाहिए।
(अधिक औपचारिक रूप से, वैरिएबल तक पहुँच के बीच volatileएक होता है-पहले संबंध बनाता है । इसका मतलब है कि पिज्जा वितरित करने से पहले किया गया एक अन्य कार्य पिज्जा प्राप्त करने वाले धागे को भी दिखाई देता है, भले ही वे अन्य परिवर्तन volatileचर में न हों ।)
सिंक्रनाइज़ किए गए तरीकों का उपयोग मुख्य रूप से पारस्परिक बहिष्करण (एक ही समय में दो चीजों को रोकने) को लागू करने के लिए किया जाता है, लेकिन उनके पास सभी समान प्रभाव volatileभी होते हैं। एक चर को पढ़ने और लिखने के दौरान उनका उपयोग करना अन्य परिवर्तनों को अन्य धागों को दिखाई देने का एक और तरीका है:
class MyHouse {
boolean pizzaArrived = false;
void eatPizza() {
while (getPizzaArrived() == false) {}
System.out.println("That was delicious!");
}
synchronized boolean getPizzaArrived() {
return pizzaArrived;
}
synchronized void deliverPizza() {
pizzaArrived = true;
}
}
एक प्रिंट स्टेटमेंट का प्रभाव
System.outएक PrintStreamवस्तु है। के तरीके PrintStreamइस तरह से सिंक्रनाइज़ हैं:
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
सिंक्रनाइज़ेशन pizzaArrivedलूप के दौरान कैश होने से बचाता है । सख्ती से बोलते हुए, दोनों थ्रेड्स को एक ही ऑब्जेक्ट पर यह गारंटी देने के लिए सिंक्रनाइज़ करना चाहिए कि चर में परिवर्तन दिखाई दे रहे हैं। (उदाहरण के लिए, printlnसेटिंग के बाद कॉल करना pizzaArrivedऔर पढ़ने से पहले उसे फिर से कॉल pizzaArrivedकरना सही होगा।) यदि किसी विशेष ऑब्जेक्ट पर केवल एक थ्रेड सिंक्रोनाइज़ करता है, तो JVM को इसे अनदेखा करने की अनुमति है। व्यवहार में, जेवीएम यह साबित करने के लिए पर्याप्त स्मार्ट नहीं है कि printlnसेटिंग के बाद अन्य धागे कॉल नहीं करेंगे pizzaArrived, इसलिए यह मानता है कि वे हो सकते हैं। इसलिए, यदि आप कॉल करते हैं, तो यह लूप के दौरान चर को कैश नहीं कर सकता है System.out.println। यही कारण है कि जब वे एक प्रिंट स्टेटमेंट रखते हैं तो इस काम को पसंद करते हैं, हालांकि यह एक सही फिक्स नहीं है।
उपयोग करना System.outइस आशय का एकमात्र तरीका नहीं है, लेकिन यह एक ऐसा व्यक्ति है जिसे सबसे अधिक बार पता चलता है, जब वे डिबग करने की कोशिश कर रहे हैं कि उनका लूप क्यों काम करता है!
बड़ी समस्या
while (pizzaArrived == false) {}एक व्यस्त-वेट लूप है। यह बुरी बात है! हालांकि यह प्रतीक्षा करता है, यह सीपीयू को हॉग करता है, जो अन्य अनुप्रयोगों को धीमा कर देता है, और सिस्टम के बिजली के उपयोग, तापमान और प्रशंसक गति को बढ़ाता है। आदर्श रूप से, हम प्रतीक्षा करते समय लूप थ्रेड को सोना चाहेंगे, इसलिए यह सीपीयू को हॉग नहीं करता है।
ये करने के कुछ तरीके इस प्रकार हैं:
प्रतीक्षा / सूचना का उपयोग करना
निम्न-स्तरीय समाधान प्रतीक्षा / अधिसूचित विधियों का उपयोग करना हैObject :
class MyHouse {
boolean pizzaArrived = false;
void eatPizza() {
synchronized (this) {
while (!pizzaArrived) {
try {
this.wait();
} catch (InterruptedException e) {}
}
}
System.out.println("That was delicious!");
}
void deliverPizza() {
synchronized (this) {
pizzaArrived = true;
this.notifyAll();
}
}
}
कोड के इस संस्करण में, लूप थ्रेड कहता है wait(), जो थ्रेड को नींद में डालता है। यह सोते समय किसी भी सीपीयू चक्र का उपयोग नहीं करेगा। दूसरा धागा चर सेट करने के बाद, यह notifyAll()किसी भी / सभी थ्रेड्स को जगाने के लिए कहता है जो उस ऑब्जेक्ट पर इंतजार कर रहे थे। यह पिज्जा के डोर बेल बजने की तरह है, इसलिए आप दरवाजे पर अजीब तरह से खड़े होने के बजाय इंतजार करते हुए आराम से बैठ सकते हैं।
किसी ऑब्जेक्ट पर प्रतीक्षा / सूचना देते समय आपको उस ऑब्जेक्ट के सिंक्रोनाइज़ेशन लॉक को पकड़ना चाहिए, जो कि उपरोक्त कोड करता है। आप किसी भी वस्तु का उपयोग कर सकते हैं, जब तक कि आप दोनों धागे एक ही वस्तु का उपयोग न करें: यहाँ मैंने उपयोग किया है this(उदाहरण MyHouse)। आमतौर पर, दो थ्रेड्स एक साथ एक ही ऑब्जेक्ट के सिंक्रनाइज़ किए गए ब्लॉक में प्रवेश करने में सक्षम नहीं होंगे (जो सिंक्रोनाइज़ेशन के उद्देश्य का हिस्सा है) लेकिन यह यहां काम करता है क्योंकि एक थ्रेड अस्थायी रूप से सिंक्रोनाइज़ेशन लॉक को रिलीज़ करता है जब यह wait()विधि के अंदर होता है ।
BlockingQueue
A BlockingQueueका उपयोग उत्पादक-उपभोक्ता कतारों को लागू करने के लिए किया जाता है। "उपभोक्ता" कतार के सामने से आइटम लेते हैं, और "निर्माता" आइटम को पीछे से धक्का देते हैं। एक उदाहरण:
class MyHouse {
final BlockingQueue<Object> queue = new LinkedBlockingQueue<>();
void eatFood() throws InterruptedException {
Object food = queue.take();
System.out.println("Eating: " + food);
}
void deliverPizza() throws InterruptedException {
queue.put("A delicious pizza");
}
}
ध्यान दें: putऔर फेंकने के takeतरीके एस, जो अपवादों की जांच कर रहे हैं जिन्हें संभालना चाहिए। उपरोक्त कोड में, सादगी के लिए, अपवादों को पुनर्विचार किया जाता है। आप विधियों में अपवादों को पकड़ना चाहते हैं और पुट को फिर से करना या कॉल करना सुनिश्चित कर सकते हैं कि यह सफल हो। इसके अलावा कुरूपता का एक बिंदु, उपयोग करने के लिए बहुत आसान है।BlockingQueueInterruptedExceptionBlockingQueue
यहां किसी अन्य सिंक्रोनाइज़ेशन की आवश्यकता नहीं है क्योंकि BlockingQueueयह सुनिश्चित करता है कि आइटमों को कतार में रखने से पहले जो कुछ भी किया गया था वह सभी आइटमों को बाहर ले जाने वाले थ्रेड्स को दिखाई देता है।
निष्पादकों
Executors तैयार किए गए BlockingQueues की तरह हैं जो कार्यों को अंजाम देते हैं। उदाहरण:
ExecutorService executor = Executors.newSingleThreadExecutor();
Runnable eatPizza = () -> { System.out.println("Eating a delicious pizza"); };
Runnable cleanUp = () -> { System.out.println("Cleaning up the house"); };
executor.execute(eatPizza);
executor.execute(cleanUp);
जानकारी के लिए दस्तावेज़ नहीं देख लिए Executor, ExecutorService, और Executors।
घटना से निपटना
UI में कुछ क्लिक करने के लिए उपयोगकर्ता की प्रतीक्षा करते समय लूपिंग करना गलत है। इसके बजाय, UI टूलकिट की ईवेंट हैंडलिंग सुविधाओं का उपयोग करें। उदाहरण के लिए, स्विंग में :
JLabel label = new JLabel();
JButton button = new JButton("Click me");
button.addActionListener((ActionEvent e) -> {
label.setText("Button was clicked");
});
क्योंकि ईवेंट हैंडलर ईवेंट डिस्पैच थ्रेड पर चलता है, ईवेंट हैंडलर में लंबे समय तक काम करना यूआई के साथ अन्य इंटरैक्शन को ब्लॉक करता है जब तक कि काम खत्म नहीं हो जाता। धीमे संचालन को एक नए धागे पर शुरू किया जा सकता है, या उपरोक्त तकनीकों में से एक का उपयोग करके प्रतीक्षा थ्रेड में भेजा जा सकता है (प्रतीक्षा / सूचना, ए BlockingQueue, या Executor)। आप एक का भी उपयोग कर सकते हैं SwingWorker, जो इसके लिए बिल्कुल तैयार किया गया है, और स्वचालित रूप से एक पृष्ठभूमि कार्यकर्ता धागा की आपूर्ति करता है:
JLabel label = new JLabel();
JButton button = new JButton("Calculate answer");
button.addActionListener((ActionEvent e) -> {
class MyWorker extends SwingWorker<String,Void> {
@Override
public String doInBackground() throws Exception {
Thread.sleep(5000);
return "Answer is 42";
}
@Override
protected void done() {
String result;
try {
result = get();
} catch (Exception e) {
throw new RuntimeException(e);
}
label.setText(result);
}
}
new MyWorker().execute();
});
टाइमर
आवधिक क्रियाएं करने के लिए, आप एक का उपयोग कर सकते हैं java.util.Timer। अपनी खुद की टाइमिंग लूप लिखने की तुलना में उपयोग करना आसान है, और शुरू करना और रोकना आसान है। यह डेमो वर्तमान समय को प्रति सेकंड एक बार प्रिंट करता है:
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println(System.currentTimeMillis());
}
};
timer.scheduleAtFixedRate(task, 0, 1000);
प्रत्येक java.util.Timerका अपना एक बैकग्राउंड थ्रेड होता है जिसका उपयोग अपने निर्धारित TimerTaskएस को निष्पादित करने के लिए किया जाता है । स्वाभाविक रूप से, थ्रेड कार्यों के बीच सोता है, इसलिए यह सीपीयू को हॉग नहीं करता है।
स्विंग कोड में, एक भी है javax.swing.Timer, जो समान है, लेकिन यह स्विंग थ्रेड पर श्रोता को निष्पादित करता है, इसलिए आप मैन्युअल रूप से थ्रेड्स स्विच करने की आवश्यकता के बिना स्विंग घटकों के साथ सुरक्षित रूप से बातचीत कर सकते हैं:
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Timer timer = new Timer(1000, (ActionEvent e) -> {
frame.setTitle(String.valueOf(System.currentTimeMillis()));
});
timer.setRepeats(true);
timer.start();
frame.setVisible(true);
दूसरा तरीका
यदि आप मल्टीथ्रेडेड कोड लिख रहे हैं, तो यह देखने के लिए इन पैकेजों में कक्षाओं को देखने लायक है कि क्या उपलब्ध है:
और जावा ट्यूटोरियल्स का कंसीडर सेक्शन भी देखें । मल्टीथ्रेडिंग जटिल है, लेकिन बहुत मदद उपलब्ध है!