परमाणु संचालन के उद्भव के लिए आवश्यक शर्तें
आइए इस उदाहरण पर एक नजर डालते हैं ताकि आपको यह समझने में मदद मिल सके कि परमाणु संचालन कैसे काम करता है:
public class Counter {
int count;
public void increment() {
count++;
}
}
जब हमारे पास एक थ्रेड होता है, तो सब कुछ बढ़िया काम करता है, लेकिन अगर हम मल्टीथ्रेडिंग जोड़ते हैं, तो हमें गलत परिणाम मिलते हैं, और सभी क्योंकि इंक्रीमेंट ऑपरेशन एक ऑपरेशन नहीं है, बल्कि तीन है: वर्तमान मूल्य प्राप्त करने का अनुरोधगिनती करना, फिर इसे 1 से बढ़ाएँ और फिर से लिखेंगिनती करना.
और जब दो धागे एक चर बढ़ाना चाहते हैं, तो आप अधिकतर डेटा खो देंगे। यही है, दोनों धागे 100 प्राप्त करते हैं, नतीजतन, दोनों 102 के अपेक्षित मूल्य के बजाय 101 लिखेंगे।
और इसे कैसे हल करें? आपको ताले का उपयोग करने की ज़रूरत है। सिंक्रोनाइज़्ड कीवर्ड इस समस्या को हल करने में मदद करता है, इसका उपयोग करने से आपको गारंटी मिलती है कि एक समय में एक थ्रेड विधि तक पहुँच प्राप्त करेगा।
public class SynchronizedCounterWithLock {
private volatile int count;
public synchronized void increment() {
count++;
}
}
साथ ही, आपको वाष्पशील कीवर्ड जोड़ने की आवश्यकता है , जो थ्रेड्स के बीच संदर्भों की सही दृश्यता सुनिश्चित करता है। हमने ऊपर उनके काम की समीक्षा की है।
लेकिन अभी भी डाउनसाइड्स हैं। सबसे बड़ा प्रदर्शन है, उस समय जब कई धागे लॉक प्राप्त करने का प्रयास कर रहे हैं और किसी को लिखने का अवसर मिलता है, शेष धागे या तो अवरुद्ध हो जाएंगे या थ्रेड जारी होने तक निलंबित कर दिए जाएंगे।
ये सभी प्रक्रियाएँ, ब्लॉक करना, दूसरी स्थिति में स्विच करना सिस्टम के प्रदर्शन के लिए बहुत महंगा है।
परमाणु संचालन
एल्गोरिथ्म निम्न-स्तरीय मशीन निर्देशों का उपयोग करता है जैसे तुलना-और-स्वैप (CAS, तुलना-और-स्वैप, जो डेटा अखंडता सुनिश्चित करता है और उन पर पहले से ही बड़ी मात्रा में शोध है)।
एक विशिष्ट CAS ऑपरेशन तीन ऑपरेंड पर संचालित होता है:
- काम के लिए मेमोरी स्पेस (एम)
- एक चर का मौजूदा अपेक्षित मूल्य (ए)।
- नया मान (B) सेट किया जाना है
CAS परमाणु रूप से M को B में अपडेट करता है, लेकिन केवल तभी जब M का मान A के समान हो, अन्यथा कोई कार्रवाई नहीं की जाती है।
पहले और दूसरे मामले में, M का मान लौटाया जाएगा। यह आपको तीन चरणों को संयोजित करने की अनुमति देता है, अर्थात्, मान प्राप्त करना, मान की तुलना करना और उसे अपडेट करना। और यह सब मशीन स्तर पर एक ऑपरेशन में बदल जाता है।
जिस क्षण एक बहु-थ्रेडेड एप्लिकेशन एक चर तक पहुंचता है और इसे अपडेट करने का प्रयास करता है और सीएएस लागू होता है, तो थ्रेड्स में से एक इसे प्राप्त करेगा और इसे अपडेट करने में सक्षम होगा। लेकिन तालों के विपरीत, अन्य थ्रेड्स को केवल मान अपडेट करने में सक्षम नहीं होने के बारे में त्रुटियां मिलेंगी। फिर वे आगे के काम के लिए आगे बढ़ेंगे, और इस प्रकार के काम में स्विचिंग को पूरी तरह से बाहर रखा गया है।
इस मामले में, तर्क इस तथ्य के कारण और अधिक कठिन हो जाता है कि हमें उस स्थिति को संभालना पड़ता है जब सीएएस ऑपरेशन सफलतापूर्वक काम नहीं करता। हम केवल कोड को मॉडल करेंगे ताकि ऑपरेशन सफल होने तक यह आगे न बढ़े।
परमाणु प्रकार का परिचय
क्या आप ऐसी स्थिति में आए हैं जहां आपको int प्रकार के सरलतम चर के लिए सिंक्रनाइज़ेशन सेट अप करने की आवश्यकता है ?
पहला तरीका जिसे हम पहले ही कवर कर चुके हैं वो है वोलेटाइल + सिंक्रोनाइज़ का उपयोग करना । लेकिन विशेष परमाणु* वर्ग भी हैं।
यदि हम CAS का उपयोग करते हैं, तो पहले तरीके की तुलना में ऑपरेशन तेजी से काम करते हैं। और इसके अलावा, हमारे पास मूल्य जोड़ने और वेतन वृद्धि और कमी संचालन के लिए विशेष और बहुत ही सुविधाजनक तरीके हैं।
AtomicBoolean , AtomicInteger , AtomicLong , AtomicIntegerArray , AtomicLongArray ऐसी कक्षाएं हैं जिनमें संचालन परमाणु होते हैं। नीचे हम उनके साथ काम का विश्लेषण करेंगे।
परमाणु पूर्णांक
AtomicInteger वर्ग एक अंतर मान पर संचालन प्रदान करता है जिसे विस्तारित परमाणु संचालन प्रदान करने के अलावा परमाणु रूप से पढ़ा और लिखा जा सकता है।
इसमें वेरिएबल्स को पढ़ने और लिखने की तरह काम करने वाले तरीके मिलते हैं और सेट होते हैं ।
यही है, "होता है-पहले" उसी चर की किसी भी बाद की प्राप्ति के साथ जिसके बारे में हमने पहले बात की थी। एटॉमिक कंपेयरएंडसेट मेथड में भी ये मेमोरी कंसिस्टेंसी फीचर होते हैं।
एक नया मान लौटाने वाले सभी ऑपरेशन परमाणु रूप से किए जाते हैं:
int addAndGet (पूर्णांक डेल्टा) | वर्तमान मान में एक विशिष्ट मान जोड़ता है। |
बूलियन तुलना औरसेट (अपेक्षित int, अपडेट int) | यदि वर्तमान मान अपेक्षित मान से मेल खाता है तो मान को दिए गए अद्यतन मान पर सेट करता है। |
int decrementAndGet () | वर्तमान मान को एक से घटाता है। |
int getAndAdd(int डेल्टा) | दिए गए मान को वर्तमान मान में जोड़ता है। |
int getAndDecrement () | वर्तमान मान को एक से घटाता है। |
int getAndIncrement () | वर्तमान मान को एक से बढ़ाता है। |
int getAndSet (int newValue) | दिए गए मान को सेट करता है और पुराना मान लौटाता है। |
इंट इंक्रीमेंट एंड गेट () | वर्तमान मान को एक से बढ़ाता है। |
आलसी सेट (इंट न्यूवैल्यू) | अंत में दिए गए मान पर सेट करें। |
बूलियन कमजोर तुलना औरसेट (अपेक्षित, अद्यतन int) | यदि वर्तमान मान अपेक्षित मान से मेल खाता है तो मान को दिए गए अद्यतन मान पर सेट करता है। |
उदाहरण:
ExecutorService executor = Executors.newFixedThreadPool(5);
IntStream.range(0, 50).forEach(i -> executor.submit(atomicInteger::incrementAndGet));
executor.shutdown();
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.HOURS);
System.out.println(atomicInteger.get()); // prints 50
GO TO FULL VERSION