2008-12-01 12 views
5

चेकस्टाइल इस कोड को "डबल-चेक किए गए लॉकिंग मुहावरे टूटा हुआ है" के रूप में रिपोर्ट करता है, लेकिन मुझे नहीं लगता कि मेरा कोड वास्तव में डबल-चेक लॉकिंग की समस्याओं से प्रभावित होता है।क्या यह टूटी हुई डबल चेकिंग है?

कोड उस डेटाबेस के साथ एक पंक्ति बनाने के लिए है यदि उस आईडी के साथ एक पंक्ति मौजूद नहीं है। यह एक बहु थ्रेडेड वातावरण में चलता है और मैं प्राथमिक-कुंजी-मौजूद SQL-अपवादों से बचना चाहता हूं।

छद्म कोड:

private void createRow(int id) { 
    Row row = dao().fetch(id); 
    if (row == null) { 
    synchronized (TestClass.class) { 
     row = dao().fetch(id); 
     if (row == null) { 
      dao().create(id); 
     } 
    } 
    } 
} 

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

क्या मैं गलत हूं या चेकस्टाइल हूं? :)

उत्तर

4

मान लिया जाये कि आप चाहते हैं कि अंतरतम लाइन को पढ़ने के लिए:

row = dao().create(id); 

यह एक क्लासिक की दोबारा जांच कर ताला dao().fetch संभालने समस्या को ठीक से बनाने के विधि से mutexed है नहीं है।

संपादित: (कोड अद्यतन किया गया था)

एक की दोबारा जांच कर ताला की क्लासिक समस्या सौंपा से पहले प्रारंभ होता है, जहां दो धागे एक ही मूल्य तक पहुँच रहे हैं एक मूल्य हो रही है।

डीएओ मानते हुए सही ढंग से सिंक्रनाइज़ किया गया है और आंशिक रूप से प्रारंभिक मान वापस नहीं करेगा, यह डबल-चेक लॉक मुहावरे की त्रुटियों से ग्रस्त नहीं है।

+1

धारणा है कि डीएओ सही ढंग से सिंक्रनाइज़ किया गया है गंभीर है। –

+0

इस धारणा के साथ समस्या यह है कि डीएओ को पता नहीं है कि आप इससे क्या उम्मीद करते हैं और अगला कोड मौका आपकी उम्मीदों को बर्बाद कर सकता है ... इसलिए, मुझे लगता है कि यह जवाब गलत है * और * खतरनाक! –

+0

क्या आप कहने का मतलब है कि डीएओ को ऑब्जेक्ट इंस्टेंस को वापस करने की उम्मीद की जानी चाहिए जो प्रारंभ नहीं हुई हैं? अगर कोई लॉक था तो क्या इससे कोई फर्क नहीं पड़ता कि क्या यह किसी भी खराब स्थिति में वस्तुओं को वापस कर सकता है? – Dustin

5

मुझे लगता है कि इस मामले में, चेकस्टाइल सही है। प्रस्तुत किए गए आपके कोड में, विचार करें कि क्या होगा यदि सिंक्रनाइज़ किए गए ब्लॉक में प्रवेश पर दो धागे दोनों row == null थे। थ्रेड ए ब्लॉक में प्रवेश करेगा, और नई पंक्ति डालेंगे। फिर थ्रेड ए के बाद ब्लॉक से निकलता है, थ्रेड बी ब्लॉक में प्रवेश करेगा (क्योंकि यह नहीं जानता कि अभी क्या हुआ), और फिर एक ही नई पंक्ति डालने का प्रयास करें।

मुझे लगता है कि आपने अभी कोड बदल दिया है और वहां एक बहुत ही महत्वपूर्ण लापता लाइन जोड़ा है। नया कोड में, आप इससे दूर हो सकते हैं, क्योंकि दो धागे साझा (स्थिर) चर में परिवर्तनों पर निर्भर नहीं होंगे। लेकिन आप यह देखकर बेहतर हो सकते हैं कि आपका डीबीएमएस INSERT OR UPDATE जैसे कथन का समर्थन करता है या नहीं।

डीबीएमएस को इस कार्यक्षमता को प्रस्तुत करने का एक और अच्छा कारण यह है कि यदि आपको कभी भी एक से अधिक एप्लिकेशन सर्वर को तैनात करने की आवश्यकता है। चूंकि synchronized ब्लॉक मशीनों पर काम नहीं करते हैं, इसलिए आपको उस मामले में कुछ और करना होगा।

+0

हाँ, कोड एक ईजेबी-कंटेनर में चल रहा है, इसलिए मुझे पता है कि मुझे वास्तव में सिंक्रनाइज़ेशन का उपयोग करने की अनुमति नहीं है क्योंकि क्लस्टर होने पर यह असफल हो जाएगा। मैं यह देखने के लिए जांच करूंगा कि क्या डीबी 2 में कोई "सम्मिलित या अपडेट करें" -फंक्शन मैं उपयोग कर सकता हूं। –

+0

तो फिर सबसे खराब मामला है कि सिंक्रनाइज़ेशन विफल रहता है और एक डालने पर दो प्रयास होते हैं (और एक विफल रहता है)? शायद सिंक्रनाइज़ेशन से परेशान न होने के लिए सबसे अच्छा है और दौड़ होने पर केवल प्रविष्टि त्रुटि को पकड़ें। – Dustin

+0

हां, यह एक आसान समाधान होगा। समस्या यह है कि मेरे आंतरिक ढांचे को ऐसा करने का मौका मिलने से पहले हमारे आंतरिक ढांचे को एसक्यूलेक्सप्शन को पकड़ता है और लॉग करता है।इसलिए मैं अपवाद को लॉग इन होने से नहीं दबा सकता और निगरानी सॉफ्टवेयर द्वारा लॉग की निगरानी की जाती है। –

3

आप इस तरह कोड लिखने के लिए परीक्षा रहे हैं, पर विचार करें:

  • जावा 1.4 के बाद से, सिंक्रनाइज़ करने के तरीकों बहुत सस्ता हो गया है। यह मुफ़्त नहीं है लेकिन रनटाइम वास्तव में इतनी पीड़ा नहीं देता है कि डेटा भ्रष्टाचार को खतरे में रखना उचित है।

  • जावा 1.5 के बाद से, आपके पास परमाणु * कक्षाएं हैं जो आपको एक परमाणु तरीके से फ़ील्ड पढ़ने और सेट करने की अनुमति देती हैं। दुर्भाग्यवश, वे आपकी समस्या का समाधान नहीं करते हैं। उन्होंने AtomicCachedReference या कुछ क्यों नहीं जोड़ा (जो प्राप्त() को कॉल करने के दौरान अतिव्यापी विधि कॉल करेगा और वर्तमान मान == शून्य) मेरे बाहर है।

  • ehcache आज़माएं। यह आपको एक कैश सेट करने की अनुमति देता है (यानी ऑब्जेक्ट जो आपको कोड कॉल करने की अनुमति देता है यदि कोई कुंजी मानचित्र में निहित है)। आमतौर पर यह वही होता है जो आप चाहते हैं और कैश वास्तव में आपकी समस्या का समाधान करते हैं (और उन सभी अन्य समस्याओं को जिन्हें आप नहीं जानते थे, वे भी अस्तित्व में थे)।

2

के रूप में अन्य लोगों ने बताया है, इस कोड को आप क्या करना चाहते हैं के रूप में है, लेकिन केवल गैर स्पष्ट मान्यताओं के एक सख्त सेट के तहत क्या करेंगे:

  1. जावा कोड गैर क्लस्टर है (देखें @ ग्रेग एच का उत्तर)
  2. "पंक्ति" संदर्भ केवल सिंक्रनाइज़ेशन ब्लॉक से पहले, पहली पंक्ति में शून्य के लिए चेक किया जा रहा है।

कारण की दोबारा जांच कर ताला लगा मुहावरा (अनुभाग Java Concurrency in Practice की 16.2.4 के अनुसार) टूट गया है कि यह इस पद्धति चलाने वाले गैर-शून्य को देखने के लिए एक धागा के लिए संभव है, लेकिन अनुचित तरीके से "पंक्ति को संदर्भ प्रारंभ है ", सिंक्रनाइज़ ब्लॉक में प्रवेश करने से पहले (जब तक" दाओ "उचित सिंक्रनाइज़ेशन प्रदान नहीं करता)। यदि आपकी विधि "पंक्ति" के साथ कुछ भी कर रही है, यह जांचने के अलावा कि यह शून्य है या नहीं, तो यह टूटा जाएगा। जैसा कि यह खड़ा है, यह शायद ठीक है लेकिन बहुत नाजुक - व्यक्तिगत रूप से मैं इस कोड को करने में सहज नहीं रहूंगा अगर मैंने सोचा कि यहां तक ​​कि एक दूरस्थ मौका भी था कि कुछ अन्य डेवलपर कुछ समय बाद इस विधि को संशोधित किए बिना विधि को संशोधित कर सकते हैं DCL।

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