थ्रेड्स कसे परस्परसंवाद करतात याच्या तपशीलांचे संक्षिप्त विहंगावलोकन. पूर्वी, आम्ही थ्रेड्स एकमेकांशी कसे सिंक्रोनाइझ केले जातात ते पाहिले. या वेळी आम्ही थ्रेड्सच्या परस्परसंवादामुळे उद्भवणार्या समस्यांबद्दल जाणून घेऊ आणि त्या कशा टाळायच्या याबद्दल आम्ही बोलू. अधिक सखोल अभ्यासासाठी आम्ही काही उपयुक्त दुवे देखील देऊ.
तुम्ही येथे एक उत्कृष्ट उदाहरण पाहू शकता: Java - Thread Starvation and Fairness . हे उदाहरण उपासमारीच्या वेळी थ्रेड्सचे काय होते ते दर्शविते आणि एक छोटासा बदल तुम्हाला भार समान रीतीने कसा वितरित करू देतो
परिचय
तर, आम्हाला माहित आहे की Java मध्ये थ्रेड्स आहेत. त्याबद्दल तुम्ही Better together: Java and the Thread class या शीर्षकाच्या पुनरावलोकनात वाचू शकता . भाग I - अंमलबजावणीचे धागे . आणि बेटर टुगेदर: Java आणि थ्रेड क्लास या पुनरावलोकनात आम्ही थ्रेड्स एकमेकांशी समक्रमित होऊ शकतात हे शोधले . भाग II — सिंक्रोनाइझेशन . थ्रेड्स एकमेकांशी कसे संवाद साधतात याबद्दल बोलण्याची वेळ आली आहे. ते सामायिक संसाधने कशी सामायिक करतात? येथे कोणत्या समस्या उद्भवू शकतात?डेडलॉक
सगळ्यात भयानक समस्या म्हणजे डेडलॉक. डेडलॉक म्हणजे जेव्हा दोन किंवा अधिक धागे अनंतकाळ दुसर्याची वाट पाहत असतात. आम्ही Oracle वेबपृष्ठावरून एक उदाहरण घेऊ जे डेडलॉकचे वर्णन करते :
public class Deadlock {
static class Friend {
private final String name;
public Friend(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public synchronized void bow(Friend bower) {
System.out.format("%s: %s bowed to me!%n",
this.name, bower.getName());
bower.bowBack(this);
}
public synchronized void bowBack(Friend bower) {
System.out.format("%s: %s bowed back to me!%n",
this.name, bower.getName());
}
}
public static void main(String[] args) {
final Friend alphonse = new Friend("Alphonse");
final Friend gaston = new Friend("Gaston");
new Thread(() -> alphonse.bow(gaston)).start();
new Thread(() -> gaston.bow(alphonse)).start();
}
}
येथे प्रथमच डेडलॉक उद्भवू शकत नाही, परंतु जर तुमचा प्रोग्राम हँग झाला असेल, तर ती चालण्याची वेळ आली आहे jvisualvm
: JVisualVM प्लगइन स्थापित करून (टूल्स -> प्लगइनद्वारे), आम्ही डेडलॉक कोठे घडले ते पाहू शकतो:
"Thread-1" - Thread t@12
java.lang.Thread.State: BLOCKED
at Deadlock$Friend.bowBack(Deadlock.java:16)
- waiting to lock <33a78231> (a Deadlock$Friend) owned by "Thread-0" t@11
थ्रेड 1 थ्रेड 0 वरून लॉकची वाट पाहत आहे. असे का होते? Thread-1
चालणे सुरू होते आणि Friend#bow
पद्धत कार्यान्वित करते. हे कीवर्डसह चिन्हांकित केले आहे , याचा अर्थ आम्ही (वर्तमान ऑब्जेक्ट) synchronized
साठी मॉनिटर घेत आहोत . this
पद्धतीचा इनपुट इतर Friend
ऑब्जेक्टचा संदर्भ होता. आता, Thread-1
दुसरी पद्धत कार्यान्वित करायची आहे Friend
, आणि तसे करण्यासाठी त्याचे लॉक घेणे आवश्यक आहे. परंतु जर दुसरा थ्रेड (या प्रकरणात Thread-0
) पद्धत प्रविष्ट करण्यात व्यवस्थापित झाला bow()
, तर लॉक आधीच विकत घेतले गेले आहे आणि Thread-1
त्याची प्रतीक्षा करत आहेThread-0
, आणि उलट. ही गतिरोध न सोडवता येणारी आहे आणि आम्ही त्याला गतिरोध म्हणतो. मृत्यूच्या पकडीप्रमाणे ज्याला सोडले जाऊ शकत नाही, डेडलॉक हे परस्पर अवरोध आहे जे तोडले जाऊ शकत नाही. डेडलॉकच्या दुसर्या स्पष्टीकरणासाठी, तुम्ही हा व्हिडिओ पाहू शकता: डेडलॉक आणि लाइव्हलॉक स्पष्ट केले .
लाइव्हलोक
डेडलॉक असल्यास, लाइव्हलॉक देखील आहे का? होय, तेथे आहे :) जेव्हा धागे बाहेरून जिवंत असल्यासारखे वाटतात, परंतु ते काहीही करू शकत नाहीत, कारण त्यांना त्यांचे कार्य सुरू ठेवण्यासाठी आवश्यक असलेली अट पूर्ण होऊ शकत नाही. मूलभूतपणे, लाइव्हलॉक डेडलॉकसारखेच आहे, परंतु थ्रेड मॉनिटरची वाट पाहत "हँग" होत नाहीत. त्याऐवजी ते कायम काहीतरी करत असतात. उदाहरणार्थ:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class App {
public static final String ANSI_BLUE = "\u001B[34m";
public static final String ANSI_PURPLE = "\u001B[35m";
public static void log(String text) {
String name = Thread.currentThread().getName(); // Like "Thread-1" or "Thread-0"
String color = ANSI_BLUE;
int val = Integer.valueOf(name.substring(name.lastIndexOf("-") + 1)) + 1;
if (val != 0) {
color = ANSI_PURPLE;
}
System.out.println(color + name + ": " + text + color);
try {
System.out.println(color + name + ": wait for " + val + " sec" + color);
Thread.currentThread().sleep(val * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Lock first = new ReentrantLock();
Lock second = new ReentrantLock();
Runnable locker = () -> {
boolean firstLocked = false;
boolean secondLocked = false;
try {
while (!firstLocked || !secondLocked) {
firstLocked = first.tryLock(100, TimeUnit.MILLISECONDS);
log("First Locked: " + firstLocked);
secondLocked = second.tryLock(100, TimeUnit.MILLISECONDS);
log("Second Locked: " + secondLocked);
}
first.unlock();
second.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(locker).start();
new Thread(locker).start();
}
}
या कोडचे यश Java थ्रेड शेड्युलर ज्या क्रमाने थ्रेड्स सुरू करते त्यावर अवलंबून असते. जर Thead-1
प्रथम प्रारंभ झाला, तर आम्हाला लाइव्हलॉक मिळेल:
Thread-1: First Locked: true
Thread-1: wait for 2 sec
Thread-0: First Locked: false
Thread-0: wait for 1 sec
Thread-0: Second Locked: true
Thread-0: wait for 1 sec
Thread-1: Second Locked: false
Thread-1: wait for 2 sec
Thread-0: First Locked: false
Thread-0: wait for 1 sec
...
जसे तुम्ही उदाहरणावरून पाहू शकता, दोन्ही थ्रेड्स दोन्ही कुलूप मिळण्याचा प्रयत्न करतात, परंतु ते अयशस्वी होतात. परंतु, ते गतिरोधक नाहीत. बाहेरून, सर्वकाही ठीक आहे आणि ते त्यांचे काम करत आहेत. JVisualVM नुसार, आम्ही झोपेचा कालावधी आणि पार्कचा कालावधी पाहतो (हे असे होते जेव्हा एखादा थ्रेड लॉक मिळवण्याचा प्रयत्न करतो — तो पार्कच्या स्थितीत प्रवेश करतो, जसे की आम्ही थ्रेड सिंक्रोनाइझेशनबद्दल बोललो तेव्हा आधी चर्चा केली होती ) . तुम्ही येथे livelock चे उदाहरण पाहू शकता: Java - Thread Livelock .
उपासमार
डेडलॉक आणि लाइव्हलॉक व्यतिरिक्त, मल्टीथ्रेडिंग दरम्यान आणखी एक समस्या उद्भवू शकते: उपासमार. ही घटना अवरोधित करण्याच्या मागील प्रकारांपेक्षा वेगळी आहे कारण थ्रेड अवरोधित केलेले नाहीत — त्यांच्याकडे पुरेसे संसाधने नाहीत. परिणामस्वरुप, काही थ्रेड्सच्या अंमलबजावणीसाठी सर्व वेळ लागत असताना, इतर चालविण्यात अक्षम आहेत:https://www.logicbig.com/
Thread.sleep()
.Thread.wait()
शर्यतीची परिस्थिती
मल्टीथ्रेडिंगमध्ये, "रेस कंडिशन" सारखी गोष्ट आहे. जेव्हा थ्रेड संसाधन सामायिक करतात तेव्हा ही घटना घडते, परंतु कोड अशा प्रकारे लिहिलेला आहे की तो योग्य सामायिकरण सुनिश्चित करत नाही. एक उदाहरण पहा:
public class App {
public static int value = 0;
public static void main(String[] args) {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
int oldValue = value;
int newValue = ++value;
if (oldValue + 1 != newValue) {
throw new IllegalStateException(oldValue + " + 1 = " + newValue);
}
}
};
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
}
}
हा कोड प्रथमच त्रुटी निर्माण करू शकत नाही. जेव्हा ते होते, तेव्हा ते असे दिसू शकते:
Exception in thread "Thread-1" java.lang.IllegalStateException: 7899 + 1 = 7901
at App.lambda$main$0(App.java:13)
at java.lang.Thread.run(Thread.java:745)
तुम्ही बघू शकता, newValue
मूल्य नियुक्त करताना काहीतरी चूक झाली. newValue
खूप मोठे आहे. value
शर्यतीच्या स्थितीमुळे, थ्रेडपैकी एकाने दोन विधानांमधील व्हेरिएबल बदलण्यात यश मिळविले . असे दिसून आले की थ्रेड्स दरम्यान एक शर्यत आहे. आता आर्थिक व्यवहारात अशाच प्रकारच्या चुका न करणे किती महत्त्वाचे आहे याचा विचार करा... उदाहरणे आणि आकृत्या येथे देखील पाहता येतील: Java थ्रेडमधील रेस कंडिशनचे अनुकरण करण्यासाठी कोड .
अस्थिर
थ्रेड्सच्या परस्परसंवादाबद्दल बोलताना,volatile
कीवर्डचा उल्लेख करणे योग्य आहे. चला एक साधे उदाहरण पाहू:
public class App {
public static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Runnable whileFlagFalse = () -> {
while(!flag) {
}
System.out.println("Flag is now TRUE");
};
new Thread(whileFlagFalse).start();
Thread.sleep(1000);
flag = true;
}
}
सर्वात मनोरंजकपणे, हे कार्य करणार नाही अशी शक्यता आहे. नवीन थ्रेडला फील्डमधील बदल दिसणार नाही flag
. फील्डसाठी याचे निराकरण करण्यासाठी flag
, आपल्याला कीवर्ड वापरण्याची आवश्यकता आहे volatile
. कसे आणि का? प्रोसेसर सर्व क्रिया करतो. परंतु गणनेचे परिणाम कुठेतरी संग्रहित केले पाहिजेत. यासाठी, मुख्य मेमरी आहे आणि प्रोसेसरची कॅशे आहे. प्रोसेसरचे कॅशे हे मेन मेमरी ऍक्सेस करण्यापेक्षा डेटा ऍक्सेस करण्यासाठी वापरल्या जाणार्या मेमरीच्या छोट्या भागासारखे असतात. परंतु प्रत्येक गोष्टीची नकारात्मक बाजू आहे: कॅशेमधील डेटा अद्ययावत असू शकत नाही (वरील उदाहरणाप्रमाणे, जेव्हा फ्लॅग फील्डचे मूल्य अद्यतनित केले गेले नाही). तर, दvolatile
कीवर्ड JVM ला सांगतो की आम्हाला आमचे व्हेरिएबल कॅशे करायचे नाही. हे सर्व थ्रेडवर अद्ययावत निकाल पाहण्यास अनुमती देते. हे एक अत्यंत सरलीकृत स्पष्टीकरण आहे. कीवर्डसाठी volatile
, मी अत्यंत शिफारस करतो की आपण हा लेख वाचा . अधिक माहितीसाठी, मी तुम्हाला Java Memory Model आणि Java Volatile Keyword वाचण्याचा सल्ला देतो . याव्यतिरिक्त, हे लक्षात ठेवणे महत्त्वाचे आहे की ते volatile
दृश्यमानतेबद्दल आहे, बदलांच्या अणूपणाबद्दल नाही. "रेस कंडिशन" विभागातील कोड पाहता, आम्हाला IntelliJ IDEA मध्ये एक टूलटिप दिसेल: ही तपासणी IntelliJ IDEA मध्ये IDEA-61117 अंकाचा भाग म्हणून जोडली गेली होती , जी 2010 मध्ये रिलीझ नोट्समध्ये सूचीबद्ध केली गेली होती .
आण्विकता
अणु ऑपरेशन्स अशी ऑपरेशन्स आहेत जी विभागली जाऊ शकत नाहीत. उदाहरणार्थ, व्हेरिएबलला मूल्य नियुक्त करण्याचे ऑपरेशन अणू असणे आवश्यक आहे. दुर्दैवाने, वाढीव ऑपरेशन अणू नाही, कारण वाढीसाठी जास्तीत जास्त तीन CPU ऑपरेशन्स आवश्यक आहेत: जुने मूल्य मिळवा, त्यात एक जोडा, नंतर मूल्य जतन करा. अणुशक्ती महत्त्वाची का आहे? वाढीव ऑपरेशनसह, शर्यतीची स्थिती असल्यास, शेअर केलेले संसाधन (म्हणजे सामायिक मूल्य) अचानक कधीही बदलू शकते. याव्यतिरिक्त, 64-बिट संरचनांचा समावेश असलेली ऑपरेशन्स, उदाहरणार्थlong
आणि double
, अणू नाहीत. अधिक तपशील येथे वाचले जाऊ शकतात: 64-बिट मूल्ये वाचताना आणि लिहिताना अणुत्वाची खात्री करा . अणुशक्तीशी संबंधित समस्या या उदाहरणात पाहिल्या जाऊ शकतात:
public class App {
public static int value = 0;
public static AtomicInteger atomic = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
value++;
atomic.incrementAndGet();
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
Thread.sleep(300);
System.out.println(value);
System.out.println(atomic.get());
}
}
विशेष AtomicInteger
वर्ग आम्हाला नेहमी 30,000 देईल, परंतु value
वेळोवेळी बदलेल. या विषयाचे एक संक्षिप्त विहंगावलोकन आहे: जावा मधील अणू चलांचा परिचय . "तुलना-आणि-स्वॅप" अल्गोरिदम अणू वर्गांच्या केंद्रस्थानी आहे. तुम्ही लॉक-फ्री अल्गोरिदम - CAS आणि FAA च्या तुलनेत JDK 7 आणि 8 च्या उदाहरणावर किंवा विकिपीडियावरील तुलना-आणि-स्वॅप लेखात याबद्दल अधिक वाचू शकता .
http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html
घडते-आधी
"आधी घडते" नावाची एक मनोरंजक आणि रहस्यमय संकल्पना आहे. थ्रेड्सच्या अभ्यासाचा भाग म्हणून, आपण त्याबद्दल वाचले पाहिजे. घडते-आधी संबंध हा क्रम दर्शवितो ज्यामध्ये थ्रेडमधील क्रिया पाहिल्या जातील. अनेक व्याख्या आणि भाष्ये आहेत. येथे या विषयावरील सर्वात अलीकडील सादरीकरणांपैकी एक आहे: Java "होते-पूर्वी" संबंध .सारांश
या पुनरावलोकनात, आम्ही थ्रेड्स कसे परस्परसंवाद करतात याचे काही तपशील शोधले आहेत. आम्ही उद्भवू शकणार्या समस्या, तसेच त्या ओळखण्याच्या आणि दूर करण्याच्या मार्गांवर चर्चा केली. विषयावरील अतिरिक्त सामग्रीची यादीः- डबल-चेक केलेले लॉकिंग
- JSR 133 (जावा मेमरी मॉडेल) FAQ
- IQ 35: गतिरोध कसा टाळायचा?
- डग्लस हॉकिन्स (2017) द्वारे जावामधील समवर्ती संकल्पना
GO TO FULL VERSION