2012-06-26 20 views
6

कहो, मैं एक डेटा वस्तु है:थ्रेड पूल, शेयर्ड डेटा, जावा तुल्यकालन

class ValueRef { double value; }

कहाँ प्रत्येक डेटा ऑब्जेक्ट एक मास्टर संग्रह में संग्रहीत है:

Collection<ValueRef> masterList = ...;

मैं भी नौकरियों का संग्रह है, जहां प्रत्येक नौकरी में डेटा ऑब्जेक्ट्स का स्थानीय संग्रह होता है (जहां प्रत्येक डेटा ऑब्जेक्ट masterList में भी दिखाई देता है):

class Job implements Runnable { 
    Collection<ValueRef> neededValues = ...; 
    void run() { 
     double sum = 0; 
     for (ValueRef x: neededValues) sum += x; 
     System.out.println(sum); 
    } 
} 

उपयोग-केस:

  1. for (ValueRef x: masterList) { x.value = Math.random(); }

  2. कुछ नौकरियों में नौकरी कतार आबाद।

  3. जागो एक धागा पूल

  4. प्रतीक्षा जब तक हर काम का मूल्यांकन किया गया है

नोट: काम मूल्यांकन के दौरान, सभी मान सभी निरंतर कर रहे हैं। हालांकि धागे ने अतीत में नौकरियों का मूल्यांकन किया है, और कैश किए गए मूल्यों को बनाए रखा है।

प्रश्न: प्रत्येक थ्रेड को नवीनतम मानों को सुनिश्चित करने के लिए आवश्यक न्यूनतम सिंक्रनाइज़ेशन क्या है?

मैं मॉनिटर/लॉक-परिप्रेक्ष्य से सिंक्रनाइज़ समझता हूं, मुझे कैश/फ्लश-परिप्रेक्ष्य से सिंक्रनाइज़ नहीं होता है (यानी सिंक्रनाइज़ किए गए ब्लॉक के प्रवेश/निकास पर स्मृति मॉडल द्वारा क्या गारंटी दी जा रही है)।

मेरे लिए, ऐसा लगता है कि मुझे थ्रेड में एक बार सिंक्रनाइज़ करने की आवश्यकता होनी चाहिए जो मानों को मुख्य स्मृति में नए मानों को करने के लिए अद्यतन करता है, और एक बार प्रति कार्यकर्ता धागा, कैश को फ्लश करने के लिए, ताकि नए मान पढ़े जा सकें। लेकिन मुझे यकीन नहीं है कि यह कैसे करना है।

मेरे दृष्टिकोण: एक वैश्विक मॉनिटर बनाने के लिए: मास्टर सूची अपडेट करते समय,, तो फिर static Object guard = new Object();guard पर सिंक्रनाइज़ करें। फिर आखिरकार, पूल में प्रत्येक थ्रेड के लिए थ्रेड पूल शुरू करने से पहले, खाली ब्लॉक में guard पर सिंक्रनाइज़ करें।

क्या यह वास्तव में उस धागे द्वारा पढ़े गए किसी भी मूल्य की पूरी फ्लश का कारण बनता है? या बस सिंक्रनाइज़ ब्लॉक के अंदर छुआ मूल्य? एक मामले में, एक खाली ब्लॉक के बजाय, शायद मुझे एक बार लूप में प्रत्येक मान को पढ़ना चाहिए?

आपके समय के लिए धन्यवाद।


संपादित करें: मुझे लगता है कि मेरे सवाल करने पर निर्भर करता है, एक बार मैं एक तुल्यकालन ब्लॉक बाहर निकलते हैं, प्रत्येक पहले पढ़ने (उस समय के बाद) मुख्य स्मृति में जाता है? भले ही मैं किस पर सिंक्रनाइज़ किया गया हो?

+0

अस्थिर कीवर्ड – ControlAltDel

+0

का लाभ लेने के लिए लगभग एक आदर्श स्थान की तरह लगता है मैं एक बार (प्रभावी रूप से स्थिर) लिख रहा हूं, लेकिन संभावित रूप से लाखों बार पढ़ रहा हूं। अस्थिरता कभी स्थानीय रूप से कैश नहीं किया जाता है। अगर मैंने हर बार थ्रेड पूल बनाया है, तो कोड ठीक w/o सिंक्रनाइज़ेशन/अस्थिर काम करेगा (क्योंकि कोई पूर्व कैश मौजूद नहीं होगा)। –

+0

मुझे यहां अस्थिरता की आवश्यकता नहीं दिख रही है। यदि ValueRef प्रभावी रूप से अपरिवर्तनीय है, तो इसे वास्तव में अपरिवर्तनीय बनाएं। डबल का प्रयोग करें। निर्धारित होने से पहले प्रत्येक नौकरी के लिए एक नया संग्रह बनाएं और अनमोडिफाइड चयन (केवल एक अनुस्मारक के रूप में) में लपेटें। आप किस समस्या का सामना करते हैं? –

उत्तर

3

इससे कोई फर्क नहीं पड़ता कि थ्रेड पूल के धागे ने अतीत में कुछ नौकरियों का मूल्यांकन किया है।

Executor की जावाडोक का कहना है:

मेमोरी स्थिरता प्रभाव: एक सूत्र में कार्रवाई से पहले एक निर्वाहक के लिए एक Runnable वस्तु प्रस्तुत करने के लिए हो-से पहले इसके निष्पादन, शुरू होता है शायद एक और धागा में।

तो, जब तक आप मानक थ्रेड पूल कार्यान्वयन का उपयोग करते हैं और नौकरियां जमा करने से पहले डेटा को बदलते हैं तो आपको स्मृति दृश्यता प्रभावों के बारे में चिंता नहीं करनी चाहिए।

+0

ऐसा इसलिए है क्योंकि ... कार्यकर्ता धागे में, एक सिंक्रनाइज़ेशन ब्लॉक नई नौकरियों के लिए इंतजार कर रहा है? और जब वह ब्लॉक निकलता है, तो थ्रेड पूरे कैश को साफ़ कर दिया जाता है? क्या मैं बस यादृच्छिक कुछ सिंक्रनाइज़ कर सकता हूं और एक ही प्रभाव प्राप्त कर सकता हूं? –

+0

@AndrewRaffensperger: इससे कोई फ़र्क नहीं पड़ता कि यह कैसे कार्यान्वित किया गया है - एक गारंटी है और इसे प्रदान किया जाना चाहिए। अंतिम प्रश्न के बारे में - मूल रूप से, लेकिन इसमें कोई अर्थ नहीं है: सिंक्रनाइज़ेशन के अतिरिक्त साधनों के बिना आप यह नहीं कह सकते कि मुख्य थ्रेड में सिंक्रनाइज़ किए गए ब्लॉक के बाद कार्यकर्ता धागे में सिंक्रनाइज़ किए गए ब्लॉक; सिंक्रनाइज़ेशन के अतिरिक्त साधनों के साथ यह अनावश्यक है। – axtavt

2

जो आप योजना बना रहे हैं वह पर्याप्त लगता है। यह इस बात पर निर्भर करता है कि आप "थ्रेड पूल को उठाने" की योजना कैसे बनाते हैं।

जावा मेमोरी मॉडल प्रदान करता है कि synchronized ब्लॉक दर्ज करने से पहले थ्रेड द्वारा किए गए सभी लेखन थ्रेड पर दिखाई देते हैं जो बाद में उस लॉक पर सिंक्रनाइज़ होते हैं।

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

हालांकि, java.util.concurrent पैकेज में उच्च स्तरीय समवर्ती उपयोगिता लागू करने के लिए मैं आपको प्रोत्साहित करता हूं। यह आपके स्वयं के समाधान से अधिक मजबूत होगा, और गहराई से पहले समेकन सीखने के लिए एक अच्छी जगह है।


बस स्पष्ट करने के लिए: यह एक सिंक्रनाइज़ ब्लॉक जहां एक चेक कार्यकर्ता लागू करने के लिए एक कार्य है या नहीं किया जाता है का उपयोग किए बिना कार्यकर्ता धागे नियंत्रित करने के लिए लगभग असंभव है। इस प्रकार, कार्यकर्ता थ्रेड द्वारा किए गए किसी भी बदलाव से कार्यकर्ता थ्रेड जागने से पहले होता है। आपको मेमोरी बाधा के रूप में कार्य करने के लिए synchronized ब्लॉक, या कम से कम volatile चर की आवश्यकता होती है; हालांकि, मैं नहीं सोच सकता कि आप इनमें से किसी एक का उपयोग करके थ्रेड पूल कैसे बनाएंगे।

java.util.concurrency पैकेज का उपयोग करने के फायदे का एक उदाहरण के रूप में, इस पर विचार करें: आप इसे में एक wait() कॉल, या एक volatile चर के साथ एक व्यस्त-प्रतीक्षा पाश के साथ एक synchronized ब्लॉक इस्तेमाल कर सकते हैं। धागे के बीच संदर्भ स्विचिंग के ऊपरी हिस्से की वजह से, एक व्यस्त प्रतीक्षा वास्तव में कुछ शर्तों के तहत बेहतर प्रदर्शन कर सकती है — यह आवश्यक नहीं है कि कोई भी पहली नज़र में भयानक विचार हो।

यदि आप कंसुरेंसी यूटिलिटीज (इस मामले में, शायद ExecutorService) का उपयोग करते हैं, तो आपके विशेष मामले के लिए सबसे अच्छा चयन आपके लिए बनाया जा सकता है, पर्यावरण में फैक्टरिंग, कार्य की प्रकृति, और अन्य धागे की ज़रूरतें एक निश्चित समय पर। ऑप्टिमाइज़ेशन के स्तर को हासिल करना आपके लिए बहुत जरूरी काम है।

+0

मैं java.util.concurrent के ऊपरी हिस्से को बर्दाश्त नहीं कर सकता। मेरे उदाहरण में डेटा एक बार अद्यतन किया जाता है, फिर बहु-थ्रेडेड मूल्यांकन के दौरान "निरंतर" बन जाता है। मुझे दिलचस्पी है कि यह डेटा अन्य पूर्व-मौजूदा धागे के लिए कैसे दिखाई देता है। ऐसा प्रतीत होता है कि किसी सिंक्रनाइज़ेशन ब्लॉक, किसी भी सिंक्रनाइज़ किए गए w/o संबंध से पहले होता है, इस दृश्यता का कारण बनता है। या शायद ऐसा होता है- इससे पहले कि किसी भी स्पष्ट सिंक्रनाइज़ेशन की आवश्यकता नहीं होती है, और "सभी कार्य परिवर्तन किए जाने तक कोई नौकरियां नहीं चलती हैं" बिल फिट बैठती है। –

+1

@AndrewRaffensperger दाएं। यदि आपको इसकी आवश्यकता है, तो शुद्धता के लिए आवश्यक न्यूनतम ओवरहेड के साथ 'java.util.concurrent' उपयोगिता है। यह मानना ​​एक गलती है कि Concurrency उपयोगिताओं के ऊपर ऊपरी है; वास्तव में, वे उच्च-प्रदर्शन समवर्ती उपकरण जैसे तुलना-और-स्वैप तक पहुंच प्रदान करते हैं। जावा में इसे स्वयं लागू करना 'AtomicXXX' वर्गों के पीछे अनुकूलित देशी कोड से धीमा होने जा रहा है। अन्य उपयोगिताओं में से अधिकांश में समान प्रदर्शन फायदे हैं। – erickson

1

आप Collection<ValueRef> और ValueRef अपरिवर्तनीय नहीं बनाते हैं या कम से कम संग्रह में संदर्भ प्रकाशित करने के बाद संग्रह में मूल्यों को संशोधित नहीं करते हैं। तो आपको सिंक्रनाइज़ेशन के बारे में कोई चिंता नहीं होगी।

वह तब होता है जब आप संग्रह के मानों को बदलना चाहते हैं, एक नया संग्रह बनाएं और इसमें नए मान डाल दें। एक बार मान सेट हो जाने के बाद संग्रह संदर्भ नई नौकरी वस्तुओं को पास कर दें।

ऐसा करने का एकमात्र कारण यह नहीं होगा कि संग्रह का आकार इतना बड़ा है कि यह स्मृति में मुश्किल से फिट बैठता है और आप दो प्रतियां नहीं ले सकते हैं, या संग्रह की स्वैपिंग के लिए बहुत अधिक काम होगा कचरा कलेक्टर (साबित करें कि थ्रेडेड कोड के लिए एक परिवर्तनीय डेटा संरचना का उपयोग करने से पहले इनमें से एक समस्या है)।

+0

ठीक है, मैं हमेशा ValueRef का पुनर्निर्माण या थ्रेड पूल का पुनर्निर्माण कर सकता हूं, और मेरी समस्या गायब हो जाती है। लेकिन मेरे वास्तविक कार्यान्वयन में, डेटा संरचना बहुत जटिल है, और कोड को अक्सर पर्याप्त कहा जाता है कि थ्रेड पूल का पुनर्निर्माण प्रत्येक मूल्यांकन बहुत अधिक ओवरहेड होगा। –

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