जावा में मेमोरी बाड़ का क्या उपयोग किया जाता है?


18

Whilst समझने की कोशिश कर रहा है कि कैसे SubmissionPublisher( जावा एसई 10 में स्रोत कोड, OpenJDK | डॉक्स ), संस्करण 9 में जावा एसई में एक नया वर्ग जोड़ा गया है, इसे लागू किया गया है, मुझे कुछ एपीआई कॉल में ठोकर लगी, जिसकी मुझे VarHandleपहले जानकारी नहीं थी:

fullFence, acquireFence, releaseFence, loadLoadFenceऔर storeStoreFence

कुछ शोध करने के बाद, विशेष रूप से स्मृति बाधाओं / बाड़ की अवधारणा के बारे में (मैंने पहले उनके बारे में सुना है, हाँ, लेकिन उनका इस्तेमाल कभी नहीं किया, इस प्रकार उनके शब्दार्थों से काफी अपरिचित थे), मुझे लगता है कि मुझे इस बात की एक बुनियादी समझ है कि वे किस लिए हैं । फिर भी, जैसा कि मेरे सवाल एक गलत धारणा से उत्पन्न हो सकते हैं, मैं यह सुनिश्चित करना चाहता हूं कि मुझे यह पहली बार में सही लगे:

  1. मेमोरी बाधाएं पढ़ने और लिखने के संचालन के संबंध में बाधाओं का पुन: निर्धारण कर रही हैं।

  2. मेमोरी बैरियर को दो मुख्य श्रेणियों में वर्गीकृत किया जा सकता है: यूनिडायरेक्शनल और बिडायरेक्शनल मेमोरी बैरियर, इस पर निर्भर करता है कि वे बाधाओं को या तो पढ़ते हैं या लिखते हैं या दोनों को सेट करते हैं।

  3. C ++ विभिन्न प्रकार की मेमोरी बाधाओं का समर्थन करता है , हालांकि, ये उनके द्वारा प्रदान किए गए लोगों के साथ मेल नहीं खाते हैं VarHandleहालाँकि, उपलब्ध स्मृति अवरोधकों में से कुछ क्रमबद्ध प्रभावVarHandle प्रदान करते हैं जो उनके संगत C ++ मेमोरी अवरोधों के अनुकूल होते हैं ।

    • #fullFence संगत है atomic_thread_fence(memory_order_seq_cst)
    • #acquireFence संगत है atomic_thread_fence(memory_order_acquire)
    • #releaseFence संगत है atomic_thread_fence(memory_order_release)
    • #loadLoadFenceऔर #storeStoreFenceकोई संगत C ++ काउंटर भाग नहीं है

शब्द संगत वास्तव में यहाँ महत्वपूर्ण लगता है क्योंकि जब यह विवरण आता है तो शब्दार्थ स्पष्ट रूप से भिन्न होता है। उदाहरण के लिए, सभी C ++ बैरियर अप्रत्यक्ष हैं, जबकि जावा के अवरोध (आवश्यक) नहीं हैं।

  1. अधिकांश मेमोरी बैरियर्स में सिंक्रोनाइज़ेशन इफेक्ट भी होते हैं। वे विशेष रूप से प्रयुक्त बाधा प्रकार और अन्य थ्रेड्स में पहले से निष्पादित बैरियर निर्देशों पर निर्भर करते हैं। पूर्ण निहितार्थ के रूप में एक बाधा निर्देश हार्डवेयर-विशिष्ट है, मैं उच्च-स्तरीय (C ++) अवरोधों के साथ रहूँगा। C ++ में, उदाहरण के लिए, परिवर्तन किए एक से पहले रिहाई बाधा अनुदेश एक धागा एक को क्रियान्वित करने के लिए दिखाई दे रहे हैं अधिग्रहण बाधा अनुदेश।

क्या मेरी धारणाएँ सही हैं? यदि हां, तो मेरे परिणामी प्रश्न हैं:

  1. क्या मेमोरी अवरोध VarHandleकिसी भी प्रकार के मेमोरी सिंक्रोनाइज़ेशन के कारण उपलब्ध हैं ?

  2. भले ही वे मेमोरी सिंक्रोनाइज़ेशन का कारण बनते हों या नहीं, जावा में फिर से काम करने में बाधाएं क्या हो सकती हैं? जावा मेमोरी मॉडल पहले से ही ऑर्डर के संबंध में कुछ बहुत मजबूत गारंटी देता है जब अस्थिर क्षेत्र, ताले या VarHandleसंचालन #compareAndSetशामिल होते हैं।

मामले में आप एक उदाहरण की तलाश कर रहे हैं: उपरोक्त BufferedSubscription, SubmissionPublisher(ऊपर से जुड़ा हुआ स्रोत) का एक आंतरिक वर्ग , लाइन 1079 (फ़ंक्शन में पूर्ण बाड़ स्थापित किया है growAndAdd; जैसा कि लिंक की गई वेबसाइट टुकड़ा पहचानकर्ताओं का समर्थन नहीं करती है, बस इसके लिए CTRL + F है। )। हालाँकि, यह मेरे लिए अस्पष्ट है कि यह किस लिए है।


1
मैंने जवाब देने की कोशिश की है, लेकिन इसे बहुत सरल बनाने के लिए, वे मौजूद हैं क्योंकि लोग जावा के मुकाबले एक कमजोर मोड चाहते हैं । आरोही क्रम में, ये होंगे plain -> opaque -> release/acquire -> volatile (sequential consistency):।
यूजीन

जवाबों:


11

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

सबसे पहली बात जो आपको समझनी होगी:

जावा लैंग्वेज स्पेसिफिकेशन (JLS) में कहीं भी बाधाओं का उल्लेख नहीं है । यह, जावा के लिए, एक कार्यान्वयन विवरण होगा: यह वास्तव में शब्दार्थ से पहले होता है । जेएमएम (जावा मेमोरी मॉडल) के अनुसार इन्हें उचित रूप से निर्दिष्ट करने में सक्षम होने के लिए, जेएमएम को काफी बदलना होगा

यह काम प्रगति में है।

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

एक और उदाहरण है जब (जावा में) आप volatileथ्रेड्स को रोकने / शुरू करने के लिए एक ध्वज को नियंत्रित करना चाहते हैं । आप जानते हैं, शास्त्रीय:

volatile boolean stop = false; // on thread writes, one thread reads this    

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

एक धागा सुरक्षित ध्वज, लेकिन अस्थिर नहीं? हाँ, बिल्कुल VarHandle::set/getOpaque:।

और आप सवाल करेंगे कि किसी को उदाहरण के लिए इसकी आवश्यकता क्यों हो सकती है? हर कोई उन सभी परिवर्तनों से रूचि नहीं रखता है जो एक द्वारा गुल्लक-समर्थित हैं volatile

आइए देखें कि हम इसे जावा में कैसे हासिल करेंगे। सबसे पहले, ऐसी विदेशी चीजें पहले से ही एपीआई में मौजूद थीं AtomicInteger::lazySet:। यह जावा मेमोरी मॉडल में अनिर्दिष्ट है और इसकी कोई स्पष्ट परिभाषा नहीं है ; अभी भी लोगों ने इसका इस्तेमाल किया (LMAX, afaik या इसे अधिक पढ़ने के लिए )। IMHO, AtomicInteger::lazySetहै VarHandle::releaseFence(या VarHandle::storeStoreFence)।


आइए उत्तर देने की कोशिश करें कि किसी को इनकी आवश्यकता क्यों है ?

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

रिलीज / अधिग्रहण से एक और अधिक छूट अपारदर्शी होगी , जिसे मैं अभी भी पूरी तरह से समझने की कोशिश कर रहा हूं


इस प्रकार नीचे की रेखा (आपकी समझ काफी सही है, btw): यदि आप जावा में इस का उपयोग करने की योजना बनाते हैं - उनके पास फिलहाल कोई विनिर्देश नहीं है, तो आप स्वयं जोखिम पर ऐसा करें। यदि आप उन्हें समझना चाहते हैं, तो उनका सी ++ समकक्ष मोड शुरू करने का स्थान है।


1
lazySetप्राचीन उत्तरों को जोड़कर इसका अर्थ जानने की कोशिश न करें , वर्तमान दस्तावेज़ ठीक यही कहता है कि इसका क्या अर्थ है, आजकल। इसके अलावा, यह कहना भ्रामक है कि झामुमो के पास केवल दो पहुँच मोड हैं। हमारे पास अस्थिर वाचन और वाष्पशील लेखन है , जो एक साथ -पहले संबंध स्थापित कर सकते हैं ।
होल्गर

1
मैं इसके बारे में कुछ और लिखने के बीच में था। इस बात पर विचार करें कि कैस एक रीड और राइट दोनों है, एक पूर्ण बाधा की तरह काम कर रहा है, और आप समझ सकते हैं कि यह क्यों आराम करना है। उदाहरण के लिए, जब ताला लगाते हैं, तो पहली क्रिया कैस (0, 1) लॉक काउंट पर होती है, लेकिन आपको केवल सिमेंटिक (जैसे वाष्पशील वाचन) प्राप्त करने की आवश्यकता होती है, जबकि अंतिम के 0 लिखने के लिए अनलॉक सिमेंटिक होना चाहिए (जैसे वाष्पशील लेखन ), इसलिए अनलॉकिंग और बाद में लॉकिंग के बीच एक घटना होती है । मोल-तोल / रिलीज अलग-अलग तालों का उपयोग करके धागे के संबंध में वाष्पशील पढ़ने / लिखने से भी कमजोर है।
होल्गर

1
@Peter Cordes: जावा के volatileपांच साल बाद पहला C संस्करण कीवर्ड वाला C99 था , लेकिन इसमें अभी भी उपयोगी शब्दार्थों की कमी थी, यहां तक ​​कि C ++ 03 में कोई मेमोरी मॉडल भी नहीं है। जिन चीजों को C ++ "परमाणु" कहता है, वे जावा से बहुत छोटी हैं। और volatileकीवर्ड परमाणु अद्यतन को भी लागू नहीं करता है। तो इसका नाम ऐसा क्यों रखा जाए।
होल्गर

1
@PeterCordes शायद, मैं इसके साथ भ्रमित कर रहा हूं restrict, हालांकि, मुझे कई बार याद है जब मुझे __volatileएक गैर-कीवर्ड बायलर एक्सटेंशन का उपयोग करने के लिए लिखना था । तो शायद, यह पूरी तरह से C89 को लागू नहीं किया? मुझे मत बताओ कि मैं वह बूढ़ा हूं । जावा 5 से पहले, volatileसी के बहुत करीब था। लेकिन जावा में कोई एमएमआईओ नहीं था, इसलिए इसका उद्देश्य हमेशा बहु-थ्रेडिंग था, लेकिन प्री-जावा 5 शब्दार्थ इसके लिए बहुत उपयोगी नहीं था। इसलिए शब्दार्थ की तरह रिलीज / अधिग्रहण को जोड़ा गया था, लेकिन फिर भी, यह परमाणु नहीं है (परमाणु अद्यतन एक अतिरिक्त विशेषता है जो इसे बनाया गया है)।
होल्गर

2
इस बारे में @ यूजीन , मेरा उदाहरण लॉकिंग के लिए कैस का उपयोग करने के लिए विशिष्ट था जिसे अधिग्रहण किया जाएगा। एक उलटी गिनती की लकीर रिलीज सिमेंटिक के साथ परमाणु घटाव को सहन करेगी, जिसके बाद थ्रेड शून्य तक पहुंच जाएगा एक बाड़ को सम्मिलित करने और अंतिम कार्रवाई को निष्पादित करेगा। बेशक, परमाणु अद्यतन के लिए अन्य मामले हैं जहां पूर्ण बाड़ की आवश्यकता है।
होल्गर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.