I ++ परमाणु क्यों नहीं है?


97

यही वजह है कि i++जावा में परमाणु नहीं है?

जावा में थोड़ा गहरा होने के लिए मैंने यह गिनने की कोशिश की कि थ्रेड्स में लूप कितनी बार निष्पादित होता है।

इसलिए मैंने ए

private static int total = 0;

मुख्य वर्ग में।

मेरे दो सूत्र हैं।

  • धागा 1: प्रिंट System.out.println("Hello from Thread 1!");
  • धागा 2: प्रिंट System.out.println("Hello from Thread 2!");

और मैं थ्रेड 1 और थ्रेड 2 द्वारा मुद्रित पंक्तियों की गणना करता हूं। लेकिन थ्रेड 1 + थ्रेड 2 की पंक्तियाँ प्रिंट की गई लाइनों की कुल संख्या से मेल नहीं खाती हैं।

यहाँ मेरा कोड है:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Test {

    private static int total = 0;
    private static int countT1 = 0;
    private static int countT2 = 0;
    private boolean run = true;

    public Test() {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        newCachedThreadPool.execute(t1);
        newCachedThreadPool.execute(t2);
        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException ex) {
            Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
        }
        run = false;
        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException ex) {
            Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println((countT1 + countT2 + " == " + total));
    }

    private Runnable t1 = new Runnable() {
        @Override
        public void run() {
            while (run) {
                total++;
                countT1++;
                System.out.println("Hello #" + countT1 + " from Thread 2! Total hello: " + total);
            }
        }
    };

    private Runnable t2 = new Runnable() {
        @Override
        public void run() {
            while (run) {
                total++;
                countT2++;
                System.out.println("Hello #" + countT2 + " from Thread 2! Total hello: " + total);
            }
        }
    };

    public static void main(String[] args) {
        new Test();
    }
}

14
आप कोशिश क्यों नहीं करते AtomicInteger?
ब्रज


3
जेवीएम में iincपूर्णांकों को बढ़ाने के लिए एक ऑपरेशन होता है, लेकिन यह केवल स्थानीय चर के लिए काम करता है, जहां संगामिति चिंता का विषय नहीं है। फ़ील्ड के लिए, कंपाइलर अलग से रीड-मॉडिफाई-राइट कमांड बनाता है।
सिली फ्रीक

14
आप इसे परमाणु होने की उम्मीद भी क्यों करेंगे?
Hot Licks

2
@ सिल्ली फ्रीक: भले ही iincखेतों के लिए एक निर्देश था , एक भी निर्देश होने से परमाणुता की गारंटी नहीं होती है, जैसे कि गैर- volatile longऔर doubleक्षेत्र तक पहुंच की गारंटी नहीं है, इस तथ्य की परवाह किए बिना परमाणु है कि यह एक एकल बायोटेक निर्देश द्वारा किया जाता है।
होल्गर

जवाबों:


125

i++शायद जावा में परमाणु नहीं है क्योंकि परमाणु एक विशेष आवश्यकता है जो के अधिकांश उपयोगों में मौजूद नहीं है i++। उस आवश्यकता का एक महत्वपूर्ण ओवरहेड है: एक वृद्धिशील ऑपरेशन परमाणु बनाने में एक बड़ी लागत है; इसमें सॉफ्टवेयर और हार्डवेयर दोनों स्तरों पर सिंक्रोनाइज़ेशन शामिल है जो एक साधारण वेतन वृद्धि में मौजूद नहीं है।

आप ऐसा तर्क बना सकते हैं जिसे i++विशेष रूप से एक परमाणु वेतन वृद्धि के रूप में डिजाइन और प्रलेखित किया जाना चाहिए, ताकि गैर-परमाणु वृद्धि का उपयोग किया जाए i = i + 1। हालांकि, यह जावा, और C और C ++ के बीच "सांस्कृतिक संगतता" को तोड़ देगा। इसके साथ ही, यह एक सुविधाजनक अंकन को दूर करेगा, जो सी-जैसी भाषाओं से परिचित प्रोग्रामर इसे प्रदान करते हैं, यह एक विशेष अर्थ देता है जो केवल सीमित परिस्थितियों में लागू होता है।

बेसिक सी या सी ++ कोड जैसे for (i = 0; i < LIMIT; i++)जावा में अनुवाद करेगा for (i = 0; i < LIMIT; i = i + 1); क्योंकि परमाणु का उपयोग करना अनुचित होगा i++। क्या बुरा है, जावा से सी या अन्य सी-जैसी भाषाओं से आने वाले प्रोग्रामर i++वैसे भी उपयोग करेंगे , जिसके परिणामस्वरूप परमाणु निर्देशों का अनावश्यक उपयोग होगा।

यहां तक ​​कि मशीन अनुदेश सेट स्तर पर, एक वृद्धि प्रकार का ऑपरेशन आमतौर पर प्रदर्शन कारणों के लिए परमाणु नहीं है। X86 में, incनिर्देश परमाणु को बनाने के लिए एक विशेष निर्देश "लॉक प्रीफिक्स" का उपयोग किया जाना चाहिए : ऊपर के समान कारणों के लिए। यदि incहमेशा परमाणु थे, तो इसका उपयोग कभी नहीं किया जाएगा जब गैर-परमाणु इंक की आवश्यकता होती है; प्रोग्रामर और कंपाइलर कोड को उत्पन्न करते हैं जो लोड करता है, 1 और स्टोर जोड़ता है, क्योंकि यह तेजी से रास्ता होगा।

कुछ निर्देश सेट आर्किटेक्चर में, कोई परमाणु incया शायद incबिल्कुल भी नहीं है; MIPS पर एक परमाणु इंक करने के लिए, आप एक सॉफ्टवेयर पाश जो का उपयोग करता है लिखने के लिए है llऔर scलोड से जुड़े, और स्टोर-सशर्त:। लोड-लिंक्ड शब्द को पढ़ता है, और स्टोर-सशर्त नए मूल्य को संग्रहीत करता है यदि शब्द नहीं बदला है, या फिर यह विफल रहता है (जो पता चला है और पुन: प्रयास का कारण बनता है)।


2
जावा के पास कोई संकेत नहीं है, स्थानीय चर को बढ़ाने से स्वाभाविक रूप से थ्रेड की बचत होती है, इसलिए लूप्स के साथ समस्या ज्यादातर खराब नहीं होगी। आपकी बात कम से कम आश्चर्य की बात है, ज़ाहिर है। यह भी, जैसा कि यह है, के i = i + 1लिए एक अनुवाद होगा ++i, न किi++
मूर्खतापूर्ण

22
प्रश्न का पहला शब्द "क्यों" है। अब तक, यह "क्यों" के मुद्दे को संबोधित करने का एकमात्र उत्तर है। अन्य जवाब वास्तव में सिर्फ सवाल को फिर से बताते हैं। तो +1।
दाऊद इब्न करीम

3
यह ध्यान देने योग्य हो सकता है कि एक परमाणु गारंटी गैर- volatileक्षेत्रों के अपडेट के लिए दृश्यता मुद्दे को हल नहीं करेगी । इसलिए जब तक कि आप हर क्षेत्र को वैसा ही नहीं मानेंगे, volatileजब एक बार एक सूत्र ने उस ++पर ऑपरेटर का उपयोग किया है, इस तरह की एक परमाणु गारंटी समवर्ती अद्यतन मुद्दों को हल नहीं करेगी। तो क्यों संभावित रूप से कुछ के लिए प्रदर्शन बर्बाद कर रहा है अगर यह समस्या को हल नहीं करता है।
होल्गर

1
@DavidWallace का मतलब यह नहीं है ++? ;)
दान Hlavenka

36

i++ दो ऑपरेशन शामिल हैं:

  1. के वर्तमान मूल्य को पढ़ें i
  2. मूल्य बढ़ाएँ और इसे असाइन करें i

जब दो धागे i++एक ही चर पर एक ही समय में प्रदर्शन करते हैं , तो वे दोनों एक ही वर्तमान मूल्य प्राप्त कर सकते हैं i, और फिर वृद्धि और इसे सेट कर सकते हैं i+1, इसलिए आपको दो के बजाय एक एकल वेतन वृद्धि मिलेगी।

उदाहरण :

int i = 5;
Thread 1 : i++;
           // reads value 5
Thread 2 : i++;
           // reads value 5
Thread 1 : // increments i to 6
Thread 2 : // increments i to 6
           // i == 6 instead of 7

(यदि परमाणु i++ था , तो भी यह अच्छी तरह से परिभाषित / थ्रेड-सुरक्षित व्यवहार नहीं होगा।)
user2864740

15
+1, लेकिन "1. ए, 2. बी और सी" तीन कार्यों की तरह लगता है, दो नहीं। :)
यशवित

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

3
@क्वेर्लेल - यदि दो प्रोसेसर एक ही स्टोरेज लोकेशन पर एक साथ एक ही ऑपरेशन को अंजाम देते हैं, और लोकेशन पर "रिजर्व" प्रसारित नहीं होता है, तो वे लगभग निश्चित रूप से हस्तक्षेप करेंगे और फर्जी परिणाम तैयार करेंगे। हां, इस ऑपरेशन के लिए "सुरक्षित" होना संभव है, लेकिन यह विशेष प्रयास लेता है, हार्डवेयर स्तर पर भी।
हॉट लिप्स

6
लेकिन मुझे लगता है कि सवाल "क्यों" था और "क्या होता है" नहीं।
सेबस्टियन मच

11

महत्वपूर्ण बात यह है कि JVM के विभिन्न कार्यान्वयनों की भाषा की एक निश्चित विशेषता को लागू किया जा सकता है या नहीं किया जा सकता है, बजाय JLS (जावा भाषा विनिर्देश)। JLS ++ 15.14.2 खंड में ++ पोस्टफिक्स ऑपरेटर को परिभाषित करता है जो कहता है कि "मान 1 को चर के मूल्य में जोड़ा जाता है और योग चर में वापस संग्रहीत होता है"। कहीं भी इसका उल्लेख या संकेत बहुपरत या परमाणु में नहीं मिलता है। इन के लिए JLS अस्थिर और प्रदान करता है सिंक्रनाइज़ । इसके अतिरिक्त, पैकेज java.util.concurrent.atomic (देखें http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/package-sumaryary.html ) है


5

जावा में i ++ परमाणु क्यों नहीं है?

आइए वेतन वृद्धि ऑपरेशन को कई कथनों में तोड़ें:

धागा 1 और 2:

  1. स्मृति से कुल का मूल्य प्राप्त करें
  2. मान में 1 जोड़ें
  3. स्मृति में वापस लिखें

यदि कोई सिंक्रनाइज़ेशन नहीं है, तो मान लें कि थ्रेड एक ने मान 3 पढ़ा है और इसे बढ़ाकर 4 कर दिया है, लेकिन इसे वापस नहीं लिखा है। इस बिंदु पर, संदर्भ स्विच होता है। थ्रेड दो मान 3 को पढ़ता है, इसे बढ़ाता है और संदर्भ स्विच होता है। यद्यपि दोनों थ्रेड्स ने कुल मूल्य में वृद्धि की है, यह अभी भी 4 - दौड़ की स्थिति होगी।


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

हां, कोई भी भाषा किसी भी विशेषता को परमाणु के रूप में परिभाषित कर सकती है लेकिन जहां तक ​​जावा को वृद्धि संचालक माना जाता है (जो कि ओपी द्वारा पोस्ट किया गया प्रश्न है) परमाणु नहीं है और मेरे उत्तर के कारण बताते हैं।
अनिकेत ठाकुर

1
(पहली टिप्पणी में मेरे कठोर स्वर के लिए खेद है) लेकिन फिर, इसका कारण यह प्रतीत होता है कि "यदि यह परमाणु होगा, तो कोई दौड़ की स्थिति नहीं होगी"। यानी, ऐसा लगता है जैसे एक दौड़ की स्थिति वांछनीय है।
सेबेस्टियन मच

@ वृद्धिशील परमाणु वृद्धि रखने के लिए शुरू किया गया ओवरहेड विशाल और शायद ही कभी वांछित है, ऑपरेशन को सस्ता रखते हुए और परिणामस्वरूप गैर परमाणु अधिकांश समय के लिए वांछनीय है।
josefx

4
@josefx: ध्यान दें कि मैं तथ्यों पर सवाल नहीं उठा रहा हूं, लेकिन इस जवाब में तर्क है। यह मूल रूप से कहता है "i ++ जावा में परमाणु नहीं है क्योंकि इसकी दौड़ की स्थिति है" , जो यह कहने जैसा है कि "एक कार के दुर्घटनाग्रस्त होने के कारण कोई एयरबैग नहीं होता है" या "आपको अपने करी-ऑर्डर के साथ कोई चाकू नहीं मिलता है क्योंकि wurst को काटना पड़ सकता है ” । इस प्रकार, मुझे नहीं लगता कि यह एक उत्तर है। सवाल यह नहीं था कि "I ++ क्या करता है?" या "I ++ का परिणाम क्या समन्वयित नहीं किया जा रहा है?"
सेबस्टियन मच

5

i++ एक कथन है जिसमें केवल 3 ऑपरेशन शामिल हैं:

  1. वर्तमान मूल्य पढ़ें
  2. नया मान लिखें
  3. नया मान संग्रहीत करें

इन तीन ऑपरेशनों को एक ही चरण में निष्पादित नहीं किया जाना चाहिए या दूसरे शब्दों i++में एक यौगिक नहीं है ऑपरेशन । परिणामस्वरूप सभी प्रकार की चीजें गलत हो सकती हैं जब एक से अधिक धागे एक ही नहीं बल्कि गैर-मिश्रित ऑपरेशन में शामिल होते हैं।

इस परिदृश्य पर विचार करें:

समय 1 :

Thread A fetches i
Thread B fetches i

समय 2 :

Thread A overwrites i with a new value say -foo-
Thread B overwrites i with a new value say -bar-
Thread B stores -bar- in i

// At this time thread B seems to be more 'active'. Not only does it overwrite 
// its local copy of i but also makes it in time to store -bar- back to 
// 'main' memory (i)

समय 3 :

Thread A attempts to store -foo- in memory effectively overwriting the -bar- 
value (in i) which was just stored by thread B in Time 2.

Thread B has nothing to do here. Its work was done by Time 2. However it was 
all for nothing as -bar- was eventually overwritten by another thread.

आखिर तुमने इसे हासिल कर ही लिया है। एक दौड़ की स्थिति।


इसलिए i++वह परमाणु नहीं है। यदि ऐसा होता, तो इसमें से कुछ भी नहीं होता और प्रत्येक fetch-update-storeपरमाणु में होता। ठीक वैसा हीAtomicInteger आपके लिए है और आपके मामले में शायद सही में फिट होगा।

पुनश्च

उन सभी मुद्दों को कवर करने वाली एक उत्कृष्ट पुस्तक और फिर कुछ इस प्रकार है: जावा कॉनएरेबिलिटी इन प्रैक्टिस


1
हम्म। एक भाषा किसी भी विशेषता को परमाणु के रूप में परिभाषित कर सकती है, चाहे वह वेतन वृद्धि हो या इकसिंगें। आप सिर्फ परमाणु नहीं होने के परिणाम का उदाहरण देते हैं।
सेबस्टियन मच

@phresnel बिल्कुल सही। लेकिन मैं यह भी बताता हूं कि यह एक एकल ऑपरेशन नहीं है जिसका विस्तार से पता चलता है कि ऐसे कई ऑपरेशनों को परमाणु में बदलने के लिए कम्प्यूटेशनल लागत बहुत अधिक महंगी है, जो बदले में -आर्थिक रूप से-उचित है कि i++परमाणु क्यों नहीं है।
kstratis

1
जब मैं आपकी बात समझ जाता हूं, तो आपका जवाब सीखने में थोड़ा भ्रमित होता है। मुझे एक उदाहरण दिखाई देता है, और एक निष्कर्ष जो कहता है "उदाहरण में स्थिति के कारण"; imho यह एक अधूरा तर्क है :(
सेबस्टियन मच

1
@phresnel शायद सबसे शैक्षणिक जवाब नहीं है, लेकिन यह सबसे अच्छा है जो मैं वर्तमान में पेश कर सकता हूं। उम्मीद है कि यह लोगों की मदद करेगा और उन्हें भ्रमित नहीं करेगा। हालांकि आलोचना के लिए धन्यवाद। मैं अपने भविष्य के पोस्ट में अधिक सटीक होने की कोशिश करूँगा।
15

2

जेवीएम में, एक वृद्धि में एक रीड एंड राइट शामिल होता है, इसलिए यह परमाणु नहीं है।


2

यदि ऑपरेशन i++परमाणु होगा, तो आपको इससे मूल्य पढ़ने का मौका नहीं मिलेगा। यह वही है जो आप उपयोग करना चाहते हैं i++( उपयोग करने के बजाय ++i)।

उदाहरण के लिए निम्नलिखित कोड देखें:

public static void main(final String[] args) {
    int i = 0;
    System.out.println(i++);
}

इस मामले में हम उम्मीद करते हैं कि आउटपुट होगा: 0 (क्योंकि हम वेतन वृद्धि करते हैं, उदाहरण के लिए पहले पढ़ें, फिर अपडेट करें)

यह एक कारण है कि ऑपरेशन परमाणु नहीं हो सकता है, क्योंकि आपको मूल्य पढ़ने की ज़रूरत है (और इसके साथ कुछ करें) और फिर मूल्य को अपडेट करें।

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


2
यह भ्रामक है। आपको निष्पादन को अलग करना होगा और परिणाम प्राप्त करना होगा, अन्यथा आप किसी भी परमाणु संचालन से मूल्य प्राप्त नहीं कर सकते ।
सेबेस्टियन मच

नहीं, ऐसा नहीं है, यही कारण है कि जावा के एटोमिकइन्तेगर को एक मिलता है (), getAndIncrement (), getAndDecrement (), incrementAndGet (), decrementAndGet () इत्यादि
रॉय वैन रिजन

1
और जावा-भाषा को i++विस्तार करने के लिए परिभाषित किया जा सकता था i.getAndIncrement()। ऐसा विस्तार नया नहीं है। जैसे, C ++ में लैम्ब्डा, C ++ में अनाम श्रेणी की परिभाषाओं तक विस्तारित है।
सेबेस्टियन मच

एक परमाणु को देखते हुए एक परमाणु या इसके विपरीत i++बना सकते ++iहैं। एक दूसरे के बराबर है।
डेविड श्वार्ट्ज

2

दो चरण हैं:

  1. स्मृति से मुझे प्राप्त करें
  2. i + 1 से i सेट करें

इसलिए यह परमाणु संचालन नहीं है। जब थ्रेड 1 i ++ निष्पादित करता है, और थ्रेड 2 i ++ निष्पादित करता है, तो i का अंतिम मान i + 1 हो सकता है।


-1

Concurrency ( Threadवर्ग और इस तरह) जावा के v1.0 में एक अतिरिक्त सुविधा है ।i++इससे पहले बीटा में जोड़ा गया था, और जैसा कि यह अभी भी इसके (अधिक या कम) मूल कार्यान्वयन में संभावना से अधिक है।

यह वैरिएबल को सिंक्रनाइज़ करने के लिए प्रोग्रामर पर निर्भर है। की जाँच करें इस पर Oracle के ट्यूटोरियल

संपादित करें: स्पष्ट करने के लिए, i ++ एक अच्छी तरह से परिभाषित प्रक्रिया है जो जावा से पूर्ववर्ती है, और जैसे जावा के डिजाइनरों ने उस प्रक्रिया की मूल कार्यक्षमता को बनाए रखने का निर्णय लिया।

++ ऑपरेटर को बी (1969) में परिभाषित किया गया था, जो कि जावा और थ्रेडिंग को एक टैड से पूर्ववर्ती करता है।


-1 "पब्लिक क्लास थ्रेड ... चूंकि: JDK1.0" स्रोत: docs.oracle.com/javase/7/docs/api/index.html?java/lang/…
मूर्खतापूर्ण

यह संस्करण इतना अधिक मायने नहीं रखता है कि यह थ्रेड क्लास से पहले लागू किया गया था और इसके कारण नहीं बदला गया था, लेकिन मैंने आपको खुश करने के लिए अपना उत्तर संपादित किया है।
TheBat

5
क्या मायने रखता है कि आपका दावा "थ्रेड वर्ग से पहले इसे लागू किया गया था" सूत्रों द्वारा समर्थित नहीं है। i++परमाणु नहीं होना एक डिजाइन निर्णय है, बढ़ती प्रणाली में एक निरीक्षण नहीं है।
सिली फ्रीक

Lol प्यारा है। i ++ को थ्रेड्स से पहले अच्छी तरह से परिभाषित किया गया था, सिर्फ इसलिए कि जावा से पहले मौजूद भाषाएं थीं। जावा के रचनाकारों ने एक अच्छी तरह से स्वीकृत प्रक्रिया को फिर से परिभाषित करने के बजाय उन अन्य भाषाओं को आधार के रूप में इस्तेमाल किया। मैंने कभी कहाँ कहा कि यह एक निरीक्षण था?
TheBat

@SillyFreak यहां कुछ स्रोत हैं जो दिखाते हैं कि ++ कितना पुराना है: en.wikipedia.org/wiki/Increment_and_decrement_operators en.wikipedia.org/wiki/B_(programming_nanguage)
TheBat
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.