जवाबों:
इसके दो मुख्य उपयोग हैं AtomicInteger
:
एक परमाणु काउंटर ( incrementAndGet()
, आदि) के रूप में जिसका उपयोग कई थ्रेड्स द्वारा समवर्ती रूप से किया जा सकता है
एक आदिम के रूप में जो गैर-अवरोधक एल्गोरिदम को लागू करने के लिए तुलना-और-स्वैप निर्देश ( compareAndSet()
) का समर्थन करता है।
यहाँ ब्रायन गॉत्ज़ के जावा कंजिरेन्सी प्रैक्टिस में गैर-ब्लॉकिंग रैंडम नंबर जनरेटर का एक उदाहरण दिया गया है :
public class AtomicPseudoRandom extends PseudoRandom {
private AtomicInteger seed;
AtomicPseudoRandom(int seed) {
this.seed = new AtomicInteger(seed);
}
public int nextInt(int n) {
while (true) {
int s = seed.get();
int nextSeed = calculateNext(s);
if (seed.compareAndSet(s, nextSeed)) {
int remainder = s % n;
return remainder > 0 ? remainder : remainder + n;
}
}
}
...
}
जैसा कि आप देख सकते हैं, यह मूल रूप से लगभग उसी तरह काम करता है incrementAndGet()
, लेकिन calculateNext()
वेतन वृद्धि (और वापसी से पहले परिणाम संसाधित करता है ) के बजाय मनमाना गणना ( ) करता है ।
read
और write that value + 1
संचालन के बीच काउंटर को संशोधित करता है , तो पुराने अपडेट को ओवरराइट करने के बजाय यह पता लगाया जाता है ("खोए हुए अपडेट" की समस्या से बचकर)। यह वास्तव में एक विशेष मामला है compareAndSet
- यदि पुराना मूल्य था 2
, तो वर्ग वास्तव में कॉल करता है compareAndSet(2, 3)
- इसलिए यदि किसी अन्य थ्रेड ने इस बीच मान को संशोधित किया है, तो वेतन वृद्धि विधि प्रभावी रूप से शुरुआत से शुरू होती है।
निरपेक्ष सरलतम उदाहरण मैं सोच सकता हूं कि परमाणु संचालन में वृद्धि करना।
मानक ints के साथ:
private volatile int counter;
public int getNextUniqueIndex() {
return counter++; // Not atomic, multiple threads could get the same result
}
परमाणु के साथ:
private AtomicInteger counter;
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
उत्तरार्द्ध सरल म्यूटेशन प्रभाव (विशेष रूप से गिनती, या अद्वितीय-अनुक्रमण) को निष्पादित करने के लिए एक बहुत ही सरल तरीका है, बिना सभी पहुंच को सिंक्रनाइज़ करने के लिए।
अधिक जटिल सिंक्रोनाइज़ेशन-फ्री लॉजिक compareAndSet()
को एक प्रकार के आशावादी लॉकिंग के रूप में उपयोग करके नियोजित किया जा सकता है - वर्तमान मूल्य प्राप्त करें, इसके आधार पर गणना करें, इस परिणाम को सेट करें यदि iff मान अभी भी गणना करने के लिए उपयोग किया जाने वाला इनपुट है, तो फिर से शुरू करें - लेकिन उदाहरणों की गिनती करना बहुत उपयोगी है, और मैं अक्सर AtomicIntegers
गिनती और वीएम-वाइड अद्वितीय जनरेटर के लिए उपयोग करूंगा यदि कोई भी एकाधिक थ्रेड्स शामिल होने का कोई संकेत नहीं है, क्योंकि वे काम करना बहुत आसान है, मैं लगभग उपयोग करने के लिए समयपूर्व अनुकूलन पर विचार करूँगा। ints
।
जब आप लगभग हमेशा एक ही सिंक्रनाइज़ेशन गारंटी के साथ ints
और उचित synchronized
घोषणाओं को प्राप्त कर सकते हैं , तो AtomicInteger
यह है कि सौंदर्य की सुरक्षा वास्तविक वस्तु में ही निर्मित होती है, बजाय इसके कि आप संभावित इंटरलेइंग और हर तरीके की निगरानी के बारे में चिंता करने की आवश्यकता है। जो int
मान तक पहुँचने के लिए होता है । getAndIncrement()
जब लौटना i++
और याद करना (या न करना) तो मॉनीटर के सही सेट को हासिल करने के बजाय गलती से थ्रेडसेफ्टी का उल्लंघन करना बहुत मुश्किल है ।
यदि आप उन तरीकों को देखते हैं जिनमें AtomicInteger के पास है, तो आप देखेंगे कि वे ints पर आम संचालन के अनुरूप हैं। उदाहरण के लिए:
static AtomicInteger i;
// Later, in a thread
int current = i.incrementAndGet();
इसका थ्रेड-सुरक्षित संस्करण है:
static int i;
// Later, in a thread
int current = ++i;
तरीकों इस तरह के नक्शे:
++i
है i.incrementAndGet()
i++
है i.getAndIncrement()
--i
है i.decrementAndGet()
i--
है i.getAndDecrement()
i = x
है i.set(x)
x = i
हैx = i.get()
अन्य सुविधा विधियाँ भी हैं, जैसे compareAndSet
याaddAndGet
का प्राथमिक उपयोग AtomicInteger
तब होता है जब आप एक मल्टीथ्रेडेड संदर्भ में होते हैं और आपको बिना उपयोग किए पूर्णांक पर थ्रेड सुरक्षित संचालन करने की आवश्यकता होती है synchronized
। आदिम प्रकार पर असाइनमेंट और पुनर्प्राप्ति int
पहले से ही परमाणु हैं लेकिन AtomicInteger
कई ऑपरेशनों के साथ आते हैं जो परमाणु नहीं हैं int
।
सबसे सरल हैं getAndXXX
या xXXAndGet
। उदाहरण के लिए getAndIncrement()
, एक परमाणु समतुल्य है i++
जो परमाणु नहीं है क्योंकि यह वास्तव में तीन ऑपरेशनों के लिए एक छोटा कट है: पुनर्प्राप्ति, जोड़ और असाइनमेंट। compareAndSet
सेमाफोर, तालों, कुंडी, आदि को लागू करने के लिए बहुत उपयोगी है।
का उपयोग करते हुए AtomicInteger
तेजी से और अधिक एक ही तुल्यकालन का उपयोग कर प्रदर्शन से पठनीय है।
एक साधारण परीक्षण:
public synchronized int incrementNotAtomic() {
return notAtomic++;
}
public void performTestNotAtomic() {
final long start = System.currentTimeMillis();
for (int i = 0 ; i < NUM ; i++) {
incrementNotAtomic();
}
System.out.println("Not atomic: "+(System.currentTimeMillis() - start));
}
public void performTestAtomic() {
final long start = System.currentTimeMillis();
for (int i = 0 ; i < NUM ; i++) {
atomic.getAndIncrement();
}
System.out.println("Atomic: "+(System.currentTimeMillis() - start));
}
मेरे पीसी पर जावा 1.6 के साथ परमाणु परीक्षण 3 सेकंड में चलता है जबकि सिंक्रनाइज़ लगभग 5.5 सेकंड में चलता है। यहां समस्या यह है कि सिंक्रनाइज़ करने के लिए ऑपरेशन ( notAtomic++
) वास्तव में कम है। इसलिए ऑपरेशन की तुलना में सिंक्रनाइज़ेशन की लागत वास्तव में महत्वपूर्ण है।
एटोमिसिटी के बगल में एटोमेन्गर को Integer
उदाहरण के लिए एक परिवर्तनशील संस्करण के रूप में उपयोग किया जा सकता है Map
।
AtomicInteger
मानचित्र कुंजी के रूप में उपयोग करना चाहता हूं , क्योंकि यह डिफ़ॉल्ट equals()
कार्यान्वयन का उपयोग करता है , जो लगभग निश्चित रूप से नहीं है जो आप मानचित्र में उपयोग किए जाने वाले शब्दार्थ की अपेक्षा करेंगे।
उदाहरण के लिए, मेरे पास एक पुस्तकालय है जो कुछ वर्ग के उदाहरण उत्पन्न करता है। इन उदाहरणों में से प्रत्येक में एक अद्वितीय पूर्णांक आईडी होना चाहिए, क्योंकि ये उदाहरण एक सर्वर पर भेजे जाने वाले आदेशों का प्रतिनिधित्व करते हैं, और प्रत्येक आदेश में एक अद्वितीय आईडी होनी चाहिए। चूंकि कई थ्रेड्स को समवर्ती रूप से कमांड भेजने की अनुमति है, इसलिए मैं उन आईडी को उत्पन्न करने के लिए एक एटोमिकइंटर का उपयोग करता हूं। एक वैकल्पिक दृष्टिकोण कुछ प्रकार के लॉक और एक नियमित पूर्णांक का उपयोग करना होगा, लेकिन यह धीमी और कम सुरुचिपूर्ण दोनों है।
जैसा कि गबुजो ने कहा, कभी-कभी मैं एटॉमिकइंन्स्टर्स का उपयोग करता हूं जब मैं संदर्भ द्वारा एक इंट पास करना चाहता हूं। यह एक बिल्ट-इन क्लास है जिसमें आर्किटेक्चर-विशिष्ट कोड है, इसलिए यह किसी भी MutableInteger की तुलना में आसान और संभावित रूप से अनुकूलित है जिसे मैं जल्दी से कोड कर सकता हूं। उस ने कहा, यह कक्षा का दुरुपयोग लगता है।
जावा में 8 परमाणु वर्गों को दो दिलचस्प कार्यों के साथ बढ़ाया गया है:
दोनों परमाणु मान का अद्यतन करने के लिए अद्यतन का उपयोग कर रहे हैं। अंतर यह है कि पहला एक पुराना मूल्य लौटाता है और दूसरा नया मूल्य वापस करता है। अद्यतन को मानक एक से अधिक जटिल "तुलना और सेट" करने के लिए लागू किया जा सकता है। उदाहरण के लिए, यह जांच सकता है कि परमाणु काउंटर शून्य से नीचे नहीं जाता है, आम तौर पर इसे सिंक्रनाइज़ेशन की आवश्यकता होगी, और यहां कोड लॉक-फ्री है:
public class Counter {
private final AtomicInteger number;
public Counter(int number) {
this.number = new AtomicInteger(number);
}
/** @return true if still can decrease */
public boolean dec() {
// updateAndGet(fn) executed atomically:
return number.updateAndGet(n -> (n > 0) ? n - 1 : n) > 0;
}
}
कोड जावा परमाणु उदाहरण से लिया गया है ।
मैं आमतौर पर AtomicInteger का उपयोग करता हूं, जब मुझे ऑब्जेक्ट्स को Ids देने की आवश्यकता होती है, जो कई थ्रेड्स से अर्जित या बनाई जा सकती हैं, और मैं आमतौर पर इसे क्लास पर एक स्थिर विशेषता के रूप में उपयोग करता हूं जो ऑब्जेक्ट के कंस्ट्रक्टर में एक्सेस करता हूं।
आप परमाणु पूर्णांक या लोंगो पर तुलनाअनवापस (CAS) का उपयोग करके गैर-अवरोधक ताले को लागू कर सकते हैं। "TL2" सॉफ्टवेयर व्यवहार स्मृति कागज इस वर्णन करता है:
हम हर ट्रांजेक्स्ड मेमोरी लोकेशन के साथ एक विशेष संस्करण लिखने-लॉक को जोड़ते हैं। अपने सरलतम रूप में, वर्क्ड राइट-लॉक एक एकल शब्द स्पिनलॉक है जो लॉक को प्राप्त करने के लिए एक CAS ऑपरेशन का उपयोग करता है और इसे रिलीज़ करने के लिए एक स्टोर। चूँकि किसी को केवल यह इंगित करने के लिए एकल बिट की आवश्यकता होती है कि लॉक लिया गया है, हम एक वर्जन नंबर को होल्ड करने के लिए बाकी लॉक वर्ड का उपयोग करते हैं।
यह जो वर्णन कर रहा है वह पहले परमाणु पूर्णांक को पढ़ा जाता है। एक उपेक्षित लॉक-बिट और संस्करण संख्या में इसे विभाजित करें। सीएएस के लिए इसे लिखने की कोशिश करें क्योंकि वर्तमान संस्करण संख्या के साथ लॉक-बिट सेट और अगले संस्करण नंबर के साथ लॉक-बिट को मंजूरी दे दी गई है। जब तक आप सफल नहीं हो जाते और लूप धागे का मालिक होता है। लॉक-बिट के साथ वर्तमान संस्करण संख्या सेट करके अनलॉक करें। कागज़ों में तालिकाओं में संस्करण संख्याओं का उपयोग करते हुए यह वर्णन करने के लिए कि थ्रेड्स के लिखे जाने पर उन्हें लगातार पढ़ने का एक सेट होता है।
यह आलेख वर्णन करता है कि प्रोसेसर के पास तुलनात्मक और स्वैप संचालन के लिए हार्डवेयर समर्थन है जो बहुत कुशल बनाता है। यह भी दावा करता है:
परमाणु चर का उपयोग करने वाले गैर-अवरुद्ध CAS- आधारित काउंटरों में कम से मध्यम विवाद के लिए लॉक-आधारित काउंटरों की तुलना में बेहतर प्रदर्शन होता है
कुंजी यह है कि वे सुरक्षित रूप से समवर्ती पहुंच और संशोधन की अनुमति देते हैं। वे आमतौर पर एक बहुस्तरीय वातावरण में काउंटरर्स के रूप में उपयोग किए जाते हैं - उनके परिचय से पहले यह एक उपयोगकर्ता लिखित वर्ग होना चाहिए जो सिंक्रनाइज़ किए गए ब्लॉकों में विभिन्न तरीकों को लपेटता था।
मैंने डाइनिंग फिलोसोफर की समस्या को हल करने के लिए एटॉमिकइंटर का उपयोग किया।
मेरे समाधान में, AtomicInteger उदाहरणों का उपयोग कांटों का प्रतिनिधित्व करने के लिए किया गया था, प्रति दार्शनिक के लिए दो आवश्यक हैं। प्रत्येक दार्शनिक को एक पूर्णांक के रूप में पहचाना जाता है, 1 के माध्यम से। 5. जब एक दार्शनिक द्वारा एक कांटा का उपयोग किया जाता है, तो AtomicInteger दार्शनिक का मान रखता है, 5 के माध्यम से 1, अन्यथा कांटा का उपयोग नहीं किया जा रहा है, इसलिए AtomicInteger का मान -1 है ।
AtomicInteger तब यह जांचने की अनुमति देता है कि क्या एक कांटा मुफ़्त है, मान == - 1, और इसे एक परमाणु ऑपरेशन में, यदि मुक्त हो, तो कांटा के मालिक को सेट करें। नीचे कोड देखें।
AtomicInteger fork0 = neededForks[0];//neededForks is an array that holds the forks needed per Philosopher
AtomicInteger fork1 = neededForks[1];
while(true){
if (Hungry) {
//if fork is free (==-1) then grab it by denoting who took it
if (!fork0.compareAndSet(-1, p) || !fork1.compareAndSet(-1, p)) {
//at least one fork was not succesfully grabbed, release both and try again later
fork0.compareAndSet(p, -1);
fork1.compareAndSet(p, -1);
try {
synchronized (lock) {//sleep and get notified later when a philosopher puts down one fork
lock.wait();//try again later, goes back up the loop
}
} catch (InterruptedException e) {}
} else {
//sucessfully grabbed both forks
transition(fork_l_free_and_fork_r_free);
}
}
}
चूँकि तुलनात्मक विधि अवरुद्ध नहीं होती है, इसलिए इसे थ्रूपुट में वृद्धि करनी चाहिए, अधिक काम करना चाहिए। जैसा कि आप जानते हैं कि डाइनिंग फिलोसोफर्स समस्या का उपयोग तब किया जाता है जब संसाधनों तक नियंत्रित पहुंच की आवश्यकता होती है, यानी कांटे की जरूरत होती है, जैसे एक प्रक्रिया को काम करने के लिए संसाधनों की आवश्यकता होती है।
तुलना के लिए सरल उदाहरणएंडसेट () फ़ंक्शन:
import java.util.concurrent.atomic.AtomicInteger;
public class GFG {
public static void main(String args[])
{
// Initially value as 0
AtomicInteger val = new AtomicInteger(0);
// Prints the updated value
System.out.println("Previous value: "
+ val);
// Checks if previous value was 0
// and then updates it
boolean res = val.compareAndSet(0, 6);
// Checks if the value was updated.
if (res)
System.out.println("The value was"
+ " updated and it is "
+ val);
else
System.out.println("The value was "
+ "not updated");
}
}
मुद्रित मूल्य है: पिछला मूल्य: 0 मान अपडेट किया गया था और यह 6 एक और सरल उदाहरण है:
import java.util.concurrent.atomic.AtomicInteger;
public class GFG {
public static void main(String args[])
{
// Initially value as 0
AtomicInteger val
= new AtomicInteger(0);
// Prints the updated value
System.out.println("Previous value: "
+ val);
// Checks if previous value was 0
// and then updates it
boolean res = val.compareAndSet(10, 6);
// Checks if the value was updated.
if (res)
System.out.println("The value was"
+ " updated and it is "
+ val);
else
System.out.println("The value was "
+ "not updated");
}
}
मुद्रित है: पिछला मान: 0 मान अद्यतन नहीं किया गया था