2012-11-15 13 views
10

जावा विनिर्देश की गारंटी देता है आदिम चर असाइनमेंट हमेशा परमाणु कर रहे हैं (long और डबल types के लिए उम्मीद है।रिलेशन

इसके विपरीत, Fetch-and-Add आपरेशन प्रसिद्ध i++ वेतन वृद्धि आपरेशन करने के लिए इसी, गैर परमाणु होगा । क्योंकि, रीड-संशोधित-लिखने आपरेशन के लिए अग्रणी

इस कोड को मान लिया जाये:

public void assign(int b) { 
    int a = b; 
} 

उत्पन्न बाईटकोड है:

public void assign(int); 
    Code: 
     0: iload_1  
     1: istore_2  
     2: return 

इस प्रकार, हम देखते हैं काम दो कदम (लदान और भंडारण) से बना है। यह जानते हुए कि X86 प्रोसेसर कर सकते हैं (कम से कम आधुनिक लोगों पर), वेतन वृद्धि आपरेशन atomically संचालित

public void assign(int); 
    Code: 
     0: iload_1  
     1: iinc   1, 1 //extra step here regarding the previous sample 
     4: istore_2  
     5: return 

, के रूप में कहा:

public void assign(int b) { 
     int i = b++; 
} 

Bytecode:

इस कोड को मान लिया जाये कि

कंप्यूटर विज्ञान में, fetch-and-add CPU निर्देश एक विशेष निर्देश है जो परमाणु रूप से संशोधित स्मृति स्थान की सामग्री। इसका उपयोग मल्टीप्रोसेसर सिस्टम में पारस्परिक बहिष्कार और समवर्ती एल्गोरिदम लागू करने के लिए किया जाता है, जो सैमफोरों का एक सामान्यीकरण है।

इस प्रकार, पहला सवाल: तथ्य यह है कि बाईटकोड दोनों चरणों (लोड हो रहा है और भंडारण) की आवश्यकता है के बावजूद, जावा तथ्य यह है कि काम आपरेशन एक ऑपरेशन हमेशा atomically जो कुछ भी प्रोसेसर की वास्तुकला और इतने किया जाता है पर निर्भर करता है अपने विनिर्देश में स्थायी परमाणु (आदिम असाइनमेंट के लिए) सुनिश्चित कर सकते हैं?

दूसरा सवाल: यह पुष्टि करते हैं कि बहुत ही आधुनिक X86 प्रोसेसर के साथ और विभिन्न आर्किटेक्चर भर में संकलित कोड साझा किए बिना गलत है, कोई जरूरत नहीं सब पर i++ आपरेशन (या AtomicInteger) सिंक्रनाइज़ करने के लिए क्या है? इसे पहले से ही परमाणु मानते हैं।

+1

मेरी समझ यह है कि असाइनमेंट की परमाणु केवल इसका अर्थ है कि आइसोर परमाणु है - दूसरे शब्दों में 'ए = बी' में, यह संभव है कि 'बी' पढ़ा जाए, फिर एक नए मान में परिवर्तित हो जाए तो मूल मान 'ए' को सौंपा गया। हालांकि, परमाणुता गारंटी देता है कि 'ए' 'बी' के 2 मूल्यों का मिश्रण नहीं होगा। – assylias

+0

@assylias मैंने यही सोचा, मैं इस परिप्रेक्ष्य से सहमत हूं :) – Mik378

उत्तर

4

को ध्यान में रखते हुए दूसरा प्रश्न

आपको लगता है कि i++ X86 Fetch-and-Add निर्देश में अनुवाद करेगा जो सत्य नहीं है। यदि कोड को संकलित और अनुकूलित किया गया है तो JVM द्वारा सत्य हो सकता है (उसे पुष्टि करने के लिए JVM का स्रोत कोड जांचना होगा), लेकिन वह कोड भी व्याख्या मोड में चलाया जा सकता है, जहां fetch और जोड़ें अलग हैं और सिंक्रनाइज़ नहीं हैं।

public class Main { 
    volatile int a; 

    static public final void main (String[] args) throws Exception { 
    new Main().run(); 
    } 

    private void run() { 
     for (int i = 0; i < 1000000; i++) { 
     increase(); 
     } 
    } 

    private void increase() { 
    a++; 
    } 
} 

मैं JVM के Java HotSpot(TM) Server VM (17.0-b12-fastdebug) for windows-x86 JRE (1.6.0_20-ea-fastdebug-b02), built on Apr 1 2010 03:25:33 संस्करण (यह एक मैंने कहीं मेरी ड्राइव पर था) का इस्तेमाल किया:

जिज्ञासा से बाहर मैं क्या विधानसभा कोड इस जावा कोड के लिए उत्पन्न होता है की जाँच की।

पहले यह इस में संकलित किया है::

00c  PUSHL EBP 
    SUB ESP,8 # Create frame 
013  MOV EBX,[ECX + #8] # int ! Field VolatileMain.a 
016  MEMBAR-acquire ! (empty encoding) 
016  MEMBAR-release ! (empty encoding) 
016  INC EBX 
017  MOV [ECX + #8],EBX ! Field VolatileMain.a 
01a  MEMBAR-volatile (unnecessary so empty encoding) 
01a  LOCK ADDL [ESP + #0], 0 ! membar_volatile 
01f  ADD ESP,8 # Destroy frame 
    POPL EBP 
    TEST PollPage,EAX ! Poll Safepoint 

029  RET 

तो यह inlined और इस में संकलित किया गया है:

0a8 B11: # B11 B12 &lt;- B10 B11 Loop: B11-B11 inner stride: not constant post of N161 Freq: 0.999997 
0a8  MOV EBX,[ESI] # int ! Field VolatileMain.a 
0aa  MEMBAR-acquire ! (empty encoding) 
0aa  MEMBAR-release ! (empty encoding) 
0aa  INC EDI 
0ab  INC EBX 
0ac  MOV [ESI],EBX ! Field VolatileMain.a 
0ae  MEMBAR-volatile (unnecessary so empty encoding) 
0ae  LOCK ADDL [ESP + #0], 0 ! membar_volatile 
0b3  CMP EDI,#1000000 
0b9  Jl,s B11 # Loop end P=0.500000 C=126282.000000 

ये इसे चलाने के महत्वपूर्ण निर्गम (java -server -XX:+PrintAssembly -cp . Main) है

जैसा कि आप देख सकते हैं कि यह a++ के लिए Fetch-And-Add निर्देशों का उपयोग नहीं करता है।

+0

+1 यदि spec एक परमाणु ऑपरेशन guarentee नहीं है इसका मतलब यह नहीं है कि एक का उपयोग नहीं किया जाएगा। बीटीडब्ल्यू: उपर्युक्त उदाहरण में मैं मानता हूं कि जेआईटी विधि को कुछ भी नहीं अनुकूलित करेगा। ;) –

+0

@ShyJ महान नमूना, धन्यवाद! – Mik378

1

अपने पहले प्रश्न के बारे में: पढ़ने और लिखने परमाणु रहे हैं, लेकिन पढ़ें/लिखें आपरेशन नहीं है। मैं पुरातन पर एक विशिष्ट संदर्भ नहीं मिल सका लेकिन JLS #17.7 का कहना है कुछ इसी तरह के बारे में संदर्भ:

लिए लिखता है और संदर्भ की पढ़ता हमेशा परमाणु कर रहे हैं, चाहे वे 32-बिट या 64-बिट मूल्यों के रूप में लागू किया जाता है की परवाह किए बिना।

तो आपके मामले में, iload और istore दोनों परमाणु हैं, लेकिन पूरे (iload, istore) ऑपरेशन नहीं है।

क्या यह गलत है [इस पर विचार करें] i ++ ऑपरेशन को सिंक्रनाइज़ करने के लिए बिल्कुल कोई आवश्यकता नहीं है?

अपने दूसरे प्रश्न के बारे में, कोड के नीचे मेरी x86 मशीन (और नहीं 1000) जो दिखाता है कि कुछ ++ अनुवाद में खो गया ==> आप ठीक ढंग से यहाँ तक कि एक प्रोसेसर आर्किटेक्चर पर एक ++ आपरेशन सिंक्रनाइज़ करने की आवश्यकता पर 982 प्रिंट जो एक fetch-and-add निर्देश का समर्थन करता है।

public class Test1 { 

    private static int i = 0; 

    public static void main(String args[]) throws InterruptedException { 
     ExecutorService executor = Executors.newFixedThreadPool(10); 
     final CountDownLatch start = new CountDownLatch(1); 
     final Set<Integer> set = new ConcurrentSkipListSet<>(); 
     Runnable r = new Runnable() { 
      @Override 
      public void run() { 
       try { 
        start.await(); 
       } catch (InterruptedException ignore) {} 
       for (int j = 0; j < 100; j++) { 
        set.add(i++); 
       } 
      } 
     }; 

     for (int j = 0; j < 10; j++) { 
      executor.submit(r); 
     } 
     start.countDown(); 
     executor.shutdown(); 
     executor.awaitTermination(1, TimeUnit.SECONDS); 
     System.out.println(set.size()); 
    } 
} 
+1

यह पुष्टि करता है कि श्याज ने क्या दावा किया है => "आप इंगित करते हैं कि i ++ X86 Fetch-and-Add निर्देश में अनुवाद करेगा जो सत्य नहीं है"। – Mik378

+0

@ मिक 378 ने आपके पहले प्रश्न पर कुछ जोड़ा। – assylias

+0

धन्यवाद, यह अब स्पष्ट है :) – Mik378

5

यहां तक ​​कि अगर मैं ++ एक x86 में अनुवाद होगा लायें-और-जोड़े अनुदेश कुछ भी नहीं बदल जाएगा क्योंकि स्मृति लायें-और-जोड़े अनुदेश में mentionned CPU के स्थानीय स्मृति registres करने के लिए और के लिए नहीं संदर्भित करता है डिवाइस/एप्लिकेशन की सामान्य स्मृति। एक आधुनिक सीपीयू पर, यह संपत्ति सीपीयू के स्थानीय मेमोरी कैश तक बढ़ेगी और मल्टीकोर सीपीयू के लिए विभिन्न कोरों द्वारा उपयोग किए जाने वाले विभिन्न कैश तक भी विस्तार कर सकती है लेकिन मल्टीथ्रेडिंग एप्लिकेशन के मामले में; पूरी तरह से कोई गारंटी नहीं है कि यह वितरण धागे द्वारा उपयोग की गई स्मृति की प्रतिलिपि में विस्तारित होगा।

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

संबंधित मुद्दे