2012-01-22 20 views
9

"अभ्यास में जावा संगामिति" एक असुरक्षित वर्ग जो जावा स्मृति मॉडल की प्रकृति की वजह से हमेशा के लिए या प्रिंट 0.जावा मेमोरी मॉडल सिंक्रनाइज़ेशन: डेटा दृश्यता बग कैसे प्रेरित करें?

मुद्दा इस वर्ग का प्रदर्शन करने की कोशिश कर रहा है चल रहा है खत्म हो सकता है की निम्न उदाहरण देता है कि चर है धागे के बीच यहां "साझा" नहीं हैं। तो थ्रेड पर मान किसी अन्य थ्रेड से अलग हो सकता है क्योंकि वे अस्थिर या सिंक्रनाइज़ नहीं होते हैं। JVM तैयार = सत्य द्वारा अनुमत बयानों की पुनरावृत्ति के कारण भी संख्या = 42 से पहले सेट हो सकता है।

मेरे लिए यह कक्षा हमेशा JVM 1.6 का उपयोग करके ठीक काम करती है। इस वर्ग को गलत व्यवहार करने के लिए कैसे प्राप्त करें इस पर कोई विचार (यानी 0 प्रिंट करें या हमेशा के लिए चलाएं)?

public class NoVisibility { 
    private static boolean ready; 
    private static int number; 

    private static class ReaderThread extends Thread { 
     public void run() { 
      while (!ready) 
       Thread.yield(); 
      System.out.println(number); 
     } 
    } 

    public static void main(String[] args) { 
     new ReaderThread().start(); 
     number = 42; 
     ready = true; 
    } 
} 
+4

लघु जवाब नहीं। लेकिन क्या आपको वास्तव में यह जांचने की ज़रूरत है कि किसी ने आपको बताया और अच्छे कारण दिए जाने के बाद छत से कूदना खतरनाक है या नहीं? – Voo

+1

2 कोर के साथ एक कंप्यूटर का उपयोग करें। –

+1

@ वू ... आप किस पृथ्वी पर बात कर रहे हैं? –

उत्तर

6

आपके पास समस्या यह है कि आप कोड को अनुकूलित करने के लिए पर्याप्त लंबे समय तक इंतजार नहीं कर रहे हैं और मूल्य कैश किया जाना चाहिए।

जब x86_64 सिस्टम पर कोई थ्रेड पहली बार मान पढ़ता है, तो उसे थ्रेड सुरक्षित प्रति प्राप्त होती है। इसके बाद के बाद में परिवर्तन यह देखने में असफल हो सकता है। यह अन्य CPUs पर मामला नहीं हो सकता है।

यदि आप इसे आजमाते हैं तो आप देख सकते हैं कि प्रत्येक थ्रेड अपने स्थानीय मूल्य से फंस गया है।

public class RequiresVolatileMain { 
    static volatile boolean value; 

    public static void main(String... args) { 
     new Thread(new MyRunnable(true), "Sets true").start(); 
     new Thread(new MyRunnable(false), "Sets false").start(); 
    } 

    private static class MyRunnable implements Runnable { 
     private final boolean target; 

     private MyRunnable(boolean target) { 
      this.target = target; 
     } 

     @Override 
     public void run() { 
      int count = 0; 
      boolean logged = false; 
      while (true) { 
       if (value != target) { 
        value = target; 
        count = 0; 
        if (!logged) 
         System.out.println(Thread.currentThread().getName() + ": reset value=" + value); 
       } else if (++count % 1000000000 == 0) { 
        System.out.println(Thread.currentThread().getName() + ": value=" + value + " target=" + target); 
        logged = true; 
       } 
      } 
     } 
    } 
} 

निम्नलिखित को प्रिंट करता है जो उसके मूल्य को फ़्लिप करता है, लेकिन अटक जाता है।

Sets true: reset value=true 
Sets false: reset value=false 
... 
Sets true: reset value=true 
Sets false: reset value=false 
Sets true: value=false target=true 
Sets false: value=true target=false 
.... 
Sets true: value=false target=true 
Sets false: value=true target=false 

अगर मैं -XX:+PrintCompilation इस स्विच समय के बारे में होता है जोड़ने जैसा कि आप देख

1705 1 % RequiresVolatileMain$MyRunnable::run @ -2 (129 bytes) made not entrant 
1705 2 % RequiresVolatileMain$MyRunnable::run @ 4 (129 bytes) 

कौन सा कोड निवासी संकलित किया गया है पता चलता है एक तरह से जो सुरक्षित थ्रेड नहीं है।

यदि आप मूल्य volatile आप इसे मूल्य बेहद flipping दिखाई देता है (या जब तक मुझे मिल ऊब)

संपादित करें: क्या इस परीक्षण करता है, जब यह मान का पता लगाता है कि धागे लक्ष्य मान नहीं है, तो यह मान निर्धारित करता है। अर्थात। थ्रेड 0 true पर सेट करता है और थ्रेड 1 सेट false पर सेट होता है जब दो थ्रेड फ़ील्ड को सही तरीके से साझा कर रहे होते हैं तो वे एक दूसरे को बदलते हैं और मान लगातार सत्य और झूठे के बीच फ़्लिप करता है।

अस्थिर बिना इस विफल रहता है और प्रत्येक थ्रेड केवल अपने स्वयं के मूल्य को देखता है, तो वे दोनों दोनों मूल्य और धागा 0 true देख सकते हैं और धागा 1 एक ही क्षेत्र के लिए false देखता है बदल रहा है।

+1

क्या आप एक लूप के साथ एक उदाहरण प्रदान कर सकते हैं जो समस्या का प्रदर्शन करेगा? –

+0

यकीन नहीं है कि मुझे यह मिल गया है! –

+0

मैंने एक स्पष्टीकरण जोड़ा है। अगर आपको अधिक संदेह/प्रश्न हैं तो मुझे बताएं। –

1

आपके ओएस के आधार पर, Thread.yield() शायद काम कर सकता है या नहीं। थ्रेड.इल्ड() को वास्तव में मंच को स्वतंत्र नहीं माना जा सकता है, और यदि आपको उस धारणा की आवश्यकता हो तो इसका उपयोग नहीं किया जाना चाहिए।

उदाहरण बनाना जो आप इसे करने की अपेक्षा करते हैं, मुझे लगता है कि यह किसी और चीज की तुलना में प्रोसेसर आर्किटेक्चर की बात है ... विभिन्न ओएस के साथ विभिन्न मशीनों पर इसे चलाने का प्रयास करें, देखें कि आप इससे क्या प्राप्त कर सकते हैं।

+0

यह थ्रेड.इल्ड के साथ बहुत कम है ... जावा में साझा मेमोरी मॉडल के बारे में। –

2

नहीं 100% यकीन है कि यह पर है, लेकिन this संबंधित हो सकती है:

क्या क्रम बदल कर क्या मतलब है?

अनेक मामलों जिसमें चर (वस्तु दृष्टान्त क्षेत्रों, वर्ग स्थिर क्षेत्रों और सरणी तत्व) कार्यक्रम के एक अलग आदेश से कार्यक्रम द्वारा निर्दिष्ट किया गया था में निष्पादित करने के लिए प्रकट हो सकता है पहुँचता रहे हैं। संकलक अनुकूलन के नाम पर निर्देशों के आदेश के साथ स्वतंत्रता लेने के लिए स्वतंत्र है। प्रोसेसर कुछ परिस्थितियों में आदेश के बाहर निर्देश निष्पादित कर सकते हैं। डेटा प्रोग्राम द्वारा निर्दिष्ट अलग-अलग क्रम में रजिस्ट्रार, प्रोसेसर कैश और मुख्य मेमोरी के बीच स्थानांतरित हो सकता है।

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

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

+0

+1 0 का प्रिंटआउट रीडरिंग के कारण है। पुस्तक से ही: "नोविज़िबिलिटी शून्य प्रिंट कर सकती है क्योंकि तैयार करने के लिए लिखने के लिए लिखने से पहले पाठक धागे को दिखाई दे सकता है, एक घटना जिसे रीडरिंग के रूप में जाना जाता है" – zengr

6

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

+0

मुझे कोई जवाब नहीं है। – zengr

+1

निश्चित रूप से एक उत्तर नहीं है लेकिन एक कच्ची वास्तविकता है! +1 – mawia

2

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

0

कृपया नीचे दिए गए कोड को देखें, यह x86 पर डेटा दृश्यता बग प्रस्तुत करता है। jdk8 साथ की कोशिश की और jdk7

package com.snippets; 


public class SharedVariable { 

    private static int sharedVariable = 0;// declare as volatile to make it work 
    public static void main(String[] args) throws InterruptedException { 

     new Thread(new Runnable() { 

      @Override 
      public void run() { 
       try { 
        Thread.sleep(1000); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } 
       sharedVariable = 1; 
      } 
     }).start(); 

     for(int i=0;i<1000;i++) { 
      for(;;) { 
       if(sharedVariable == 1) { 
        break; 
       } 
      } 
     } 
     System.out.println("Value of SharedVariable : " + sharedVariable); 
    } 

} 

चाल प्रोसेसर पुनर्व्यवस्था बल्कि कुछ अनुकूलन जो दृश्यता बग का परिचय कराने के लिए संकलक बनाने करने की उम्मीद नहीं है।

यदि आप उपर्युक्त कोड चलाते हैं तो आप देखेंगे कि यह अनिश्चित काल तक लटका हुआ है क्योंकि यह कभी भी अपडेट किए गए मूल्य को साझा नहीं करता है।

कोड को ठीक करने के अस्थिर रूप sharedVariable घोषणा करते हैं।

क्यों सामान्य चर काम नहीं किया और इसके बाद के संस्करण कार्यक्रम लटका हुआ है?

  1. साझा किए गए वैरिएबल को अस्थिर घोषित नहीं किया गया था।
  2. अब क्योंकि साझा किए जाने योग्य को घोषित नहीं किया गया था क्योंकि अस्थिर कंपाइलर कोड को अनुकूलित करता है। यह देखता है कि साझा किए जा सकने योग्य नहीं बदला जा रहा है, इसलिए मुझे लूप में हर बार स्मृति से क्यों पढ़ना चाहिए। यह साझा लापरवाही लूप से बाहर ले जाएगा। नीचे कुछ समान है।

for(int i=0;i<1000;i++)/**compiler reorders sharedVariable 
as it is not declared as volatile 
and takes out the if condition out of the loop 
which is valid as compiler figures out that it not gonna 
change sharedVariable is not going change **/ 
    if(sharedVariable != 1) { 
    for(;;) {} 
    }  
} 

GitHub पर साझा: https://github.com/lazysun/concurrency/blob/master/Concurrency/src/com/snippets/SharedVariable.java

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