Java Language
परमाणु प्रकार
खोज…
परिचय
जावा परमाणु प्रकार सरल परिवर्तनशील प्रकार हैं जो बुनियादी संचालन प्रदान करते हैं जो लॉकिंग का सहारा लिए बिना थ्रेड-सुरक्षित और परमाणु हैं। वे ऐसे मामलों में उपयोग के लिए अभिप्रेत हैं, जहां ताला लगाना एक निर्णायक अड़चन होगी, या जहां गतिरोध या लाइवलॉक का जोखिम है।
पैरामीटर
पैरामीटर | विवरण |
---|---|
सेट | क्षेत्र का अस्थिर सेट |
प्राप्त | क्षेत्र का वाष्पशील वाचन |
lazySet | यह एक स्टोर है जो क्षेत्र के संचालन का आदेश देता है |
compareAndSet | यदि मान एक्सपेक्ट मूल्य है तो इसे नए मूल्य पर भेज दिया |
getAndSet | वर्तमान मूल्य और अद्यतन प्राप्त करें |
टिप्पणियों
अनिवार्य रूप से अस्थिर वाचन या लिखने और CAS संचालन के कई संयोजन। इसे समझने का सबसे अच्छा तरीका सीधे सोर्स कोड को देखना है। ईजी एटोमिकइंटर , अनसेफ.गेटएंडसेट
परमाणु प्रकार बनाना
सरल बहु-थ्रेडेड कोड के लिए, सिंक्रनाइज़ेशन का उपयोग करना स्वीकार्य है। हालाँकि, सिंक्रोनाइज़ेशन का उपयोग करने पर एक लीनियर प्रभाव पड़ता है, और जैसा कि एक कोडबेस अधिक जटिल हो जाता है, संभावना बढ़ जाती है कि आप डेडलॉक , भुखमरी या लाइवलॉक के साथ समाप्त हो जाएंगे ।
अधिक जटिल संगामिति के मामलों में, परमाणु चर का उपयोग करना अक्सर एक बेहतर विकल्प होता है, क्योंकि यह एक अलग-अलग चर को सिंक्रनाइज़ तरीकों या कोड ब्लॉकों का उपयोग किए बिना ओवरहेड-सुरक्षित तरीके से एक्सेस करने की अनुमति देता है।
एक AtomicInteger
प्रकार बनाना:
AtomicInteger aInt = new AtomicInteger() // Create with default value 0
AtomicInteger aInt = new AtomicInteger(1) // Create with initial value 1
इसी तरह अन्य उदाहरणों के लिए।
AtomicIntegerArray aIntArray = new AtomicIntegerArray(10) // Create array of specific length
AtomicIntegerArray aIntArray = new AtomicIntegerArray(new int[] {1, 2, 3}) // Initialize array with another array
इसी प्रकार अन्य परमाणु प्रकारों के लिए।
एक उल्लेखनीय अपवाद है कि कोई float
और double
प्रकार नहीं है। इन के उपयोग के माध्यम नकली जा सकती है Float.floatToIntBits(float)
और Float.intBitsToFloat(int)
के लिए float
के साथ-साथ Double.doubleToLongBits(double)
और Double.longBitsToDouble(long)
डबल्स के लिए।
आप उपयोग करना चाहते हैं, तो sun.misc.Unsafe
आप में परमाणु आपरेशन का उपयोग करके परमाणु रूप में किसी भी आदिम चर का उपयोग कर सकते हैं sun.misc.Unsafe
। सभी आदिम प्रकारों को इंट या लॉन्ग में परिवर्तित या एन्कोड किया जाना चाहिए ताकि वे इस तरह से इसका उपयोग कर सकें। इस पर अधिक के लिए देखें: sun.misc.Unsafe ।
परमाणु प्रकार के लिए प्रेरणा
बहु-थ्रेडेड अनुप्रयोगों को लागू करने का सरल तरीका जावा के अंतर्निहित सिंक्रनाइज़ेशन और लॉकिंग प्राइमेटिक्स का उपयोग करना है; जैसे कि synchronized
कीवर्ड। निम्न उदाहरण से पता चलता है कि हम कैसे उपयोग कर सकते हैं synchronized
संचित मायने रखता है।
public class Counters {
private final int[] counters;
public Counters(int nosCounters) {
counters = new int[nosCounters];
}
/**
* Increments the integer at the given index
*/
public synchronized void count(int number) {
if (number >= 0 && number < counters.length) {
counters[number]++;
}
}
/**
* Obtains the current count of the number at the given index,
* or if there is no number at that index, returns 0.
*/
public synchronized int getCount(int number) {
return (number >= 0 && number < counters.length) ? counters[number] : 0;
}
}
यह कार्यान्वयन सही ढंग से काम करेगा। हालाँकि, यदि आपके पास एक ही Counters
ऑब्जेक्ट पर बहुत सी एक साथ कॉल करने वाले थ्रेड्स हैं, तो सिंक्रोनाइज़ेशन एक अड़चन के रूप में उत्तरदायी है। विशेष रूप से:
- प्रत्येक
synchronized
विधि कॉल चालू थ्रेड के साथ प्रारंभ होगी जोCounters
आवृत्ति के लिए लॉक प्राप्त कर रहा है। - थ्रेड
number
मान की जांच करने और काउंटर को अपडेट करने के दौरान लॉक को होल्ड करेगा। - अंत में, यह लॉक को रिलीज़ करेगा, जिससे अन्य थ्रेड्स का उपयोग किया जा सकेगा।
यदि एक थ्रेड लॉक को प्राप्त करने का प्रयास करता है, जबकि दूसरा इसे धारण करता है, तो प्रयास थ्रेड को तब तक रोक दिया जाता है जब तक कि लॉक जारी नहीं हो जाता। यदि कई सूत्र प्रतीक्षा कर रहे हैं, तो उनमें से एक इसे प्राप्त करेगा, और अन्य अवरुद्ध रहेंगे।
इससे कुछ समस्याएं हो सकती हैं:
यदि लॉक के लिए बहुत अधिक विवाद है (यानी बहुत सारे धागे इसे हासिल करने की कोशिश करते हैं), तो कुछ धागे लंबे समय तक अवरुद्ध हो सकते हैं।
जब एक थ्रेड लॉक के इंतजार में अवरुद्ध हो जाता है, तो ऑपरेटिंग सिस्टम आमतौर पर एक अलग थ्रेड पर स्विच निष्पादन का प्रयास करेगा। यह संदर्भ स्विचिंग प्रोसेसर पर अपेक्षाकृत बड़े प्रदर्शन प्रभाव डालता है।
जब एक ही लॉक पर कई थ्रेड्स ब्लॉक किए जाते हैं, तो इस बात की कोई गारंटी नहीं है कि उनमें से किसी एक को "निष्पक्ष रूप से" माना जाएगा (यानी प्रत्येक थ्रेड को चलाने के लिए निर्धारित होने की गारंटी है)। इससे थ्रेड भुखमरी हो सकती है।
परमाणु प्रकार कैसे लागू करता है?
आइए हम AtomicInteger
काउंटर का उपयोग करके ऊपर दिए गए उदाहरण को फिर से लिखना शुरू करें:
public class Counters {
private final AtomicInteger[] counters;
public Counters(int nosCounters) {
counters = new AtomicInteger[nosCounters];
for (int i = 0; i < nosCounters; i++) {
counters[i] = new AtomicInteger();
}
}
/**
* Increments the integer at the given index
*/
public void count(int number) {
if (number >= 0 && number < counters.length) {
counters[number].incrementAndGet();
}
}
/**
* Obtains the current count of the object at the given index,
* or if there is no number at that index, returns 0.
*/
public int getCount(int number) {
return (number >= 0 && number < counters.length) ?
counters[number].get() : 0;
}
}
हमने एक AtomicInteger[]
int[]
साथ int[]
को प्रतिस्थापित किया है, और इसे प्रत्येक तत्व में एक उदाहरण के साथ आरंभीकृत किया है। हमने int
मानों पर संचालन के स्थान पर incrementAndGet()
और get()
कॉल भी जोड़े हैं।
लेकिन सबसे महत्वपूर्ण बात यह है कि हम synchronized
कीवर्ड को हटा सकते हैं क्योंकि लॉकिंग की आवश्यकता नहीं है। यह काम करता है क्योंकि incrementAndGet()
और get()
संचालन परमाणु और थ्रेड-सुरक्षित हैं । इस संदर्भ में, इसका मतलब है कि:
सरणी में प्रत्येक काउंटर केवल एक ऑपरेशन के लिए "पहले" राज्य (जैसे "वेतन वृद्धि") में या "बाद में" राज्य में अवलोकन योग्य होगा।
यह मानते हुए कि ऑपरेशन
T
समय पर होता है, कोई भी धागाT
बाद "राज्य" से पहले नहीं देख पाएगा।
इसके अलावा, जबकि दो थ्रेड वास्तव में एक ही समय में एक ही AtomicInteger
इंस्टेंस को अपडेट करने का प्रयास कर सकते हैं, संचालन के कार्यान्वयन यह सुनिश्चित करते हैं कि दिए गए इंस्टेंस पर एक समय में केवल एक वेतन वृद्धि होती है। यह लॉकिंग के बिना किया जाता है, जिसके परिणामस्वरूप अक्सर बेहतर प्रदर्शन होता है।
परमाणु प्रकार कैसे काम करते हैं?
परमाणु प्रकार आम तौर पर लक्ष्य मशीन के निर्देश सेट में विशेष हार्डवेयर निर्देशों पर निर्भर करते हैं। उदाहरण के लिए, इंटेल-आधारित निर्देश सेट एक CAS
( तुलना और स्वैप ) निर्देश प्रदान करते हैं जो स्मृति संचालन के विशिष्ट अनुक्रम को परमाणु रूप से निष्पादित करेंगे।
इन निम्न-स्तरीय निर्देशों का उपयोग संबंधित AtomicXxx
वर्गों के एपीआई में उच्च-स्तरीय संचालन को लागू करने के लिए किया जाता है। उदाहरण के लिए, (फिर से, सी-लाइक स्यूडोकोड में):
private volatile num;
int increment() {
while (TRUE) {
int old = num;
int new = old + 1;
if (old == compare_and_swap(&num, old, new)) {
return new;
}
}
}
अगर AtomicXxxx
पर कोई विवाद नहीं है, तो if
परीक्षण सफल होगा और लूप तुरंत समाप्त हो जाएगा। यदि कोई विवाद है, तो if
सभी थ्रेड्स में से एक के लिए विफल हो जाएगा, और वे लूप के चक्रों की एक छोटी संख्या के लिए लूप में "स्पिन" करेंगे। व्यवहार में, कताई तेजी से तीव्रता का आदेश है (विवाद के उच्च स्तर पर छोड़कर, जहां सिंक्रनाइज़ेशन परमाणु वर्गों की तुलना में बेहतर प्रदर्शन करता है क्योंकि जब सीएएस ऑपरेशन विफल हो जाता है, तो धागा को निलंबित करने और दूसरे पर स्विच करने की तुलना में रिट्री केवल अधिक विवाद जोड़ देगा)। एक।
संयोग से, CAS निर्देश आमतौर पर JVM द्वारा अनियोजित लॉकिंग को लागू करने के लिए उपयोग किया जाता है। यदि JVM देख सकता है कि एक ताला वर्तमान में बंद नहीं है, तो यह ताला प्राप्त करने के लिए CAS का उपयोग करने का प्रयास करेगा। यदि कैस सफल होता है, तो महंगा धागा निर्धारण, संदर्भ स्विचिंग आदि करने की आवश्यकता नहीं है। उपयोग की गई तकनीकों के बारे में अधिक जानकारी के लिए, हॉटस्पॉट में बायस्ड लॉकिंग देखें।