AtomicInteger के लिए व्यावहारिक उपयोग


228

मैं समझता हूं कि एटॉमिकइंटर और अन्य एटॉमिक वैरिएबल समवर्ती एक्सेस की अनुमति देते हैं। इस वर्ग को आमतौर पर किन मामलों में इस्तेमाल किया जाता है?

जवाबों:


189

इसके दो मुख्य उपयोग हैं 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()वेतन वृद्धि (और वापसी से पहले परिणाम संसाधित करता है ) के बजाय मनमाना गणना ( ) करता है ।


1
मुझे लगता है कि मैं पहला प्रयोग समझता हूं। यह सुनिश्चित करने के लिए है कि किसी विशेषता को फिर से एक्सेस करने से पहले काउंटर को बढ़ा दिया गया है। सही बात? क्या आप दूसरे उपयोग के लिए एक छोटा उदाहरण दे सकते हैं?
जेम्स पी।

8
पहले उपयोग की आपकी समझ एक तरह से सही है - यह केवल यह सुनिश्चित करता है कि यदि कोई अन्य थ्रेड readऔर write that value + 1संचालन के बीच काउंटर को संशोधित करता है , तो पुराने अपडेट को ओवरराइट करने के बजाय यह पता लगाया जाता है ("खोए हुए अपडेट" की समस्या से बचकर)। यह वास्तव में एक विशेष मामला है compareAndSet- यदि पुराना मूल्य था 2, तो वर्ग वास्तव में कॉल करता है compareAndSet(2, 3)- इसलिए यदि किसी अन्य थ्रेड ने इस बीच मान को संशोधित किया है, तो वेतन वृद्धि विधि प्रभावी रूप से शुरुआत से शुरू होती है।
आंद्रेज डॉयल

3
"शेष> 0; शेष: शेष + एन?" इस अभिव्यक्ति में 0 से शेष n को जोड़ने का कारण है?
सैंडपीकुनकुरु

100

निरपेक्ष सरलतम उदाहरण मैं सोच सकता हूं कि परमाणु संचालन में वृद्धि करना।

मानक 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++और याद करना (या न करना) तो मॉनीटर के सही सेट को हासिल करने के बजाय गलती से थ्रेडसेफ्टी का उल्लंघन करना बहुत मुश्किल है ।


2
इस स्पष्ट स्पष्टीकरण के लिए धन्यवाद। एक कक्षा में एक एटोमिकइंटर का उपयोग करने के क्या फायदे होंगे जहां सभी तरीके सिंक्रनाइज़ किए जाते हैं? क्या बाद वाले को "भारी" माना जाएगा?
जेम्स पी।

3
मेरे नजरिए से, यह मुख्य रूप से आपके द्वारा एटॉमिकइंटरेयर्स के साथ मिलने वाला एनकैप्सुलेशन है - सिंक्रोनाइज़ेशन ठीक उसी तरह से होता है, जिसकी आपको आवश्यकता होती है, और आपको वर्णनात्मक तरीके मिलते हैं जो सार्वजनिक एपीआई में होते हैं ताकि समझा जा सके कि इच्छित परिणाम क्या है। (प्लस कुछ हद तक आप सही हैं, अक्सर एक वर्ग में सभी तरीकों को सिंक्रनाइज़ करने के लिए समाप्त हो जाएगा, जो संभवतः बहुत मोटे तौर पर दानेदार है, हालांकि हॉटस्पॉट लॉक लॉक ऑप्टिमाइजेशन और समय से पहले अनुकूलन के खिलाफ नियमों के साथ, मैं पठनीयता पर विचार करता हूं प्रदर्शन से अधिक लाभ।)
आंद्रेजेज डॉयल

यह बहुत स्पष्ट और सटीक व्याख्या है, धन्यवाद !!
आकाश 5288

अंत में एक स्पष्टीकरण जिसने इसे मेरे लिए ठीक से मंजूरी दे दी।
बेनी बोटेमा

58

यदि आप उन तरीकों को देखते हैं जिनमें 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


37

का प्राथमिक उपयोग 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


1
मुझे नहीं लगता कि मैं AtomicIntegerमानचित्र कुंजी के रूप में उपयोग करना चाहता हूं , क्योंकि यह डिफ़ॉल्ट equals()कार्यान्वयन का उपयोग करता है , जो लगभग निश्चित रूप से नहीं है जो आप मानचित्र में उपयोग किए जाने वाले शब्दार्थ की अपेक्षा करेंगे।
आंद्रेज डॉयल

1
@Andrzej यकीन है, कुंजी के रूप में नहीं है जो unmutable लेकिन एक मूल्य के लिए आवश्यक हैं।
गबुजो २ g

@gabuzo कोई भी विचार क्यों परमाणु पूर्णांक सिंक्रनाइज़ पर अच्छा प्रदर्शन करता है?
सुपुन वाइजरथने

यह परीक्षण अभी काफी पुराना है (6 वर्ष से अधिक) यह मुझे हाल ही में जेआरई के साथ फिर से खेलना दिलचस्प हो सकता है। मैं इसका जवाब देने के लिए AtomicInteger में पर्याप्त रूप से नहीं गया था, लेकिन यह एक बहुत ही विशिष्ट कार्य है यह सिंक्रनाइज़ेशन तकनीकों का उपयोग करेगा जो केवल इस विशिष्ट मामले में काम कर रहे हैं। यह भी ध्यान रखें कि परीक्षण नीरस है और भारी भार वाले वातावरण में इसी तरह का परीक्षण करने से परमाणुभक्षक के लिए इतनी स्पष्ट जीत नहीं मिल सकती है
gabuzo

मेरा विश्वास है कि 3 एमएस और 5.5 एमएस
सथेश

17

उदाहरण के लिए, मेरे पास एक पुस्तकालय है जो कुछ वर्ग के उदाहरण उत्पन्न करता है। इन उदाहरणों में से प्रत्येक में एक अद्वितीय पूर्णांक आईडी होना चाहिए, क्योंकि ये उदाहरण एक सर्वर पर भेजे जाने वाले आदेशों का प्रतिनिधित्व करते हैं, और प्रत्येक आदेश में एक अद्वितीय आईडी होनी चाहिए। चूंकि कई थ्रेड्स को समवर्ती रूप से कमांड भेजने की अनुमति है, इसलिए मैं उन आईडी को उत्पन्न करने के लिए एक एटोमिकइंटर का उपयोग करता हूं। एक वैकल्पिक दृष्टिकोण कुछ प्रकार के लॉक और एक नियमित पूर्णांक का उपयोग करना होगा, लेकिन यह धीमी और कम सुरुचिपूर्ण दोनों है।


इस व्यावहारिक उदाहरण को साझा करने के लिए धन्यवाद। यह कुछ ऐसा लगता है जिसका मुझे उपयोग करना चाहिए क्योंकि मुझे अपने कार्यक्रम में आयात की जाने वाली प्रत्येक फ़ाइल के लिए अद्वितीय आईडी की आवश्यकता है :)
जेम्स पी।

7

जैसा कि गबुजो ने कहा, कभी-कभी मैं एटॉमिकइंन्स्टर्स का उपयोग करता हूं जब मैं संदर्भ द्वारा एक इंट पास करना चाहता हूं। यह एक बिल्ट-इन क्लास है जिसमें आर्किटेक्चर-विशिष्ट कोड है, इसलिए यह किसी भी MutableInteger की तुलना में आसान और संभावित रूप से अनुकूलित है जिसे मैं जल्दी से कोड कर सकता हूं। उस ने कहा, यह कक्षा का दुरुपयोग लगता है।


7

जावा में 8 परमाणु वर्गों को दो दिलचस्प कार्यों के साथ बढ़ाया गया है:

  • int getAndUpdate (IntUnaryOperator updateFunction)
  • int updateAndGet (IntUnaryOperator updateFunction)

दोनों परमाणु मान का अद्यतन करने के लिए अद्यतन का उपयोग कर रहे हैं। अंतर यह है कि पहला एक पुराना मूल्य लौटाता है और दूसरा नया मूल्य वापस करता है। अद्यतन को मानक एक से अधिक जटिल "तुलना और सेट" करने के लिए लागू किया जा सकता है। उदाहरण के लिए, यह जांच सकता है कि परमाणु काउंटर शून्य से नीचे नहीं जाता है, आम तौर पर इसे सिंक्रनाइज़ेशन की आवश्यकता होगी, और यहां कोड लॉक-फ्री है:

    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;
      }
    }

कोड जावा परमाणु उदाहरण से लिया गया है ।


5

मैं आमतौर पर AtomicInteger का उपयोग करता हूं, जब मुझे ऑब्जेक्ट्स को Ids देने की आवश्यकता होती है, जो कई थ्रेड्स से अर्जित या बनाई जा सकती हैं, और मैं आमतौर पर इसे क्लास पर एक स्थिर विशेषता के रूप में उपयोग करता हूं जो ऑब्जेक्ट के कंस्ट्रक्टर में एक्सेस करता हूं।


4

आप परमाणु पूर्णांक या लोंगो पर तुलनाअनवापस (CAS) का उपयोग करके गैर-अवरोधक ताले को लागू कर सकते हैं। "TL2" सॉफ्टवेयर व्यवहार स्मृति कागज इस वर्णन करता है:

हम हर ट्रांजेक्स्ड मेमोरी लोकेशन के साथ एक विशेष संस्करण लिखने-लॉक को जोड़ते हैं। अपने सरलतम रूप में, वर्क्ड राइट-लॉक एक एकल शब्द स्पिनलॉक है जो लॉक को प्राप्त करने के लिए एक CAS ऑपरेशन का उपयोग करता है और इसे रिलीज़ करने के लिए एक स्टोर। चूँकि किसी को केवल यह इंगित करने के लिए एकल बिट की आवश्यकता होती है कि लॉक लिया गया है, हम एक वर्जन नंबर को होल्ड करने के लिए बाकी लॉक वर्ड का उपयोग करते हैं।

यह जो वर्णन कर रहा है वह पहले परमाणु पूर्णांक को पढ़ा जाता है। एक उपेक्षित लॉक-बिट और संस्करण संख्या में इसे विभाजित करें। सीएएस के लिए इसे लिखने की कोशिश करें क्योंकि वर्तमान संस्करण संख्या के साथ लॉक-बिट सेट और अगले संस्करण नंबर के साथ लॉक-बिट को मंजूरी दे दी गई है। जब तक आप सफल नहीं हो जाते और लूप धागे का मालिक होता है। लॉक-बिट के साथ वर्तमान संस्करण संख्या सेट करके अनलॉक करें। कागज़ों में तालिकाओं में संस्करण संख्याओं का उपयोग करते हुए यह वर्णन करने के लिए कि थ्रेड्स के लिखे जाने पर उन्हें लगातार पढ़ने का एक सेट होता है।

यह आलेख वर्णन करता है कि प्रोसेसर के पास तुलनात्मक और स्वैप संचालन के लिए हार्डवेयर समर्थन है जो बहुत कुशल बनाता है। यह भी दावा करता है:

परमाणु चर का उपयोग करने वाले गैर-अवरुद्ध CAS- आधारित काउंटरों में कम से मध्यम विवाद के लिए लॉक-आधारित काउंटरों की तुलना में बेहतर प्रदर्शन होता है


3

कुंजी यह है कि वे सुरक्षित रूप से समवर्ती पहुंच और संशोधन की अनुमति देते हैं। वे आमतौर पर एक बहुस्तरीय वातावरण में काउंटरर्स के रूप में उपयोग किए जाते हैं - उनके परिचय से पहले यह एक उपयोगकर्ता लिखित वर्ग होना चाहिए जो सिंक्रनाइज़ किए गए ब्लॉकों में विभिन्न तरीकों को लपेटता था।


समझा। क्या यह उन मामलों में है जहां एक विशेषता या उदाहरण किसी अनुप्रयोग के अंदर एक वैश्विक चर के रूप में कार्य करता है। या ऐसे अन्य मामले हैं जिनके बारे में आप सोच सकते हैं?
जेम्स पी।

1

मैंने डाइनिंग फिलोसोफर की समस्या को हल करने के लिए एटॉमिकइंटर का उपयोग किया।

मेरे समाधान में, 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);
        }
    }
}

चूँकि तुलनात्मक विधि अवरुद्ध नहीं होती है, इसलिए इसे थ्रूपुट में वृद्धि करनी चाहिए, अधिक काम करना चाहिए। जैसा कि आप जानते हैं कि डाइनिंग फिलोसोफर्स समस्या का उपयोग तब किया जाता है जब संसाधनों तक नियंत्रित पहुंच की आवश्यकता होती है, यानी कांटे की जरूरत होती है, जैसे एक प्रक्रिया को काम करने के लिए संसाधनों की आवश्यकता होती है।


0

तुलना के लिए सरल उदाहरणएंडसेट () फ़ंक्शन:

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 मान अद्यतन नहीं किया गया था

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.