2010-02-14 13 views
14

नीचे दी गई कक्षा में, विधि getIt() थ्रेड सुरक्षित है और क्यों?जावा थ्रेड्स सिंक्रनाइज़ेशन

public class X { 
    private long myVar; 
    public void setIt(long var){ 
    myVar = var; 
    } 
    public long getIt() { 
    return myVar; 
    } 
} 
+0

क्या यह होमवर्क है? या एक साक्षात्कार सवाल? – finnw

+3

@finnw: जब भी आप पसंद करते हैं :) अंतर क्या है ...? अगर आपने योगदान नहीं दिया ...? क्या आप चाहते हैं कि मैं टैग बदलूं? –

+0

मैं सिर्फ उत्सुक था क्योंकि मुझे साक्षात्कार में एक समान प्रश्न पूछा गया है। टैग बदलने की जरूरत नहीं है। – finnw

उत्तर

22

यह थ्रेड-सुरक्षित नहीं है। जावा में long और double के चर के दो अलग-अलग 32-बिट चर के रूप में माना जाता है। एक धागा लिख ​​सकता है और आधा मान लिखा है जब एक और धागा दोनों हिस्सों को पढ़ता है। इस स्थिति में, पाठक को एक ऐसा मूल्य दिखाई देगा जो कभी अस्तित्व में नहीं था।

इस सूत्र-सुरक्षित आप घोषणा कर सकते हैं या तो myVarvolatile के रूप में (जावा 1.5 या बाद में) बनाने के लिए या दोनों setIt और getItsynchronized बनाते हैं।

ध्यान दें कि myVar एक 32-बिट int था, तब भी आप थ्रेडिंग समस्याओं में भाग सकते हैं जहां एक थ्रेड किसी पुराने थ्रेड को बदलकर पुराने मूल्य को पढ़ सकता है। ऐसा इसलिए हो सकता है क्योंकि सीपीयू द्वारा मूल्य कैश किया गया है। इसे हल करने के लिए, आपको फिर से myVarvolatile (जावा 1.5 या बाद में) घोषित करने की आवश्यकता है या setIt और getItsynchronized दोनों को घोषित करने की आवश्यकता है।

यह भी ध्यान देने योग्य है कि यदि आप getIt के परिणामस्वरूप setIt कॉल में परिणाम का उपयोग कर रहे हैं, उदा। x.setIt(x.getIt() * 2), तो आप शायद दोनों कॉल को भर में synchronize हैं:

synchronized(x) 
{ 
    x.setIt(x.getIt() * 2); 
} 

अतिरिक्त तुल्यकालन के बिना, एक और धागा getIt और setIt कॉल के बीच अन्य धागा के मूल्य के कारण खो दिया जा करने के लिए मूल्य बदल सकता है।

+0

+1 यह एक तथ्य है कि अधिकांश डेवलपर्स अनजान हैं। अस्थिर होने के बजाय, आप अपने इरादे को बेहतर व्यक्त करने के लिए परमाणु * वर्गों (java.util.concurrent में पाए गए) का भी उपयोग कर सकते हैं। – helpermethod

6

नहीं, ऐसा नहीं है। कम से कम, प्लेटफॉर्म पर नहीं, जिसमें परमाणु 64-बिट मेमोरी एक्सेस की कमी है।

स्मृति जहां समर्थन मूल्य है, और फिर पूर्व empted है इससे पहले कि यह अन्य 32 बिट कॉपी कर सकते हैं में मान लीजिए कि धागा एक कॉल setIt, प्रतियां 32 बिट।

फिर थ्रेड बी getIt पर कॉल करता है।

+7

परमाणु 64-बिट मेमोरी एक्सेस प्रदान करने वाले प्लेटफ़ॉर्म पर भी, आपको प्राप्त करने और सेट विधियों को सिंक्रनाइज़ करना होगा। या आपको सदस्य को अस्थिर बनाना है। अधिक जानकारी के लिए मेरा जवाब देखें। – tangens

+0

आपका उत्तर सही है, लेकिन बहुत विशिष्ट है। इस बारे में अधिक सामान्य कारण हैं कि यह सिंक्रनाइज़ क्यों नहीं है - @tangens उत्तर देखें। –

2

यह होना चाहिए, और आमतौर पर यह होना चाहिए, लेकिन गारंटीकृत नहीं है। सीपीयू कैश में अलग-अलग संस्करणों वाले विभिन्न कोरों के साथ समस्याएं हो सकती हैं, या दुकान/सभी आर्किटेक्चर के लिए परमाणु नहीं होने पर पुनर्प्राप्त हो सकती है। AtomicLong कक्षा का प्रयोग करें।

-4

चूंकि यह केवल पढ़ने की विधि है। आपको set विधि सिंक्रनाइज़ करना चाहिए।

EDIT: मुझे लगता है कि विधि विधि को सिंक्रनाइज़ करने की आवश्यकता क्यों है। फिल रॉस समझाया अच्छा काम।

+10

यह निश्चित रूप से थ्रेडसेफ बनाने के लिए दोनों विधियों को सिंक्रनाइज़ किया जाना चाहिए।अन्यथा, प्राप्त विधि आंशिक रूप से परिवर्तित संस्करण उत्पन्न कर सकता है। –

3

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

तो अंतिम परिणाम यह है कि यह एक ऐसा मूल्य देता है जो कभी वैध नहीं था।

9

यह थ्रेड-सुरक्षित नहीं है।यहां तक ​​कि यदि आपका मंच long पर परमाणु लिखने की गारंटी देता है, तो synchronized की कमी से यह संभव हो जाता है कि एक थ्रेड setIt() पर कॉल करता है और इस कॉल के समाप्त होने के बाद भी यह संभव है कि कोई अन्य थ्रेड getIt() पर कॉल कर सके और यह कॉल myVar का पुराना मान वापस कर सके।

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

तो आपको या तो दोनों विधियों को synchronized के रूप में चिह्नित करना होगा या सदस्य myVar को volatile के रूप में चिह्नित करना होगा।

तुल्यकालन here के बारे में एक बहुत अच्छा स्पष्टीकरण नहीं दिया गया है:

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

0

गेटर थ्रेड सुरक्षित नहीं है क्योंकि यह किसी भी तंत्र द्वारा संरक्षित नहीं है जो सबसे अद्यतित दृश्यता की गारंटी देता है। आपके विकल्प हैं:

  • myVar अंतिम (लेकिन फिर आप इसे उत्परिवर्तित नहीं कर सकता)
  • बनाने myVar अस्थिर
  • उपयोग सिंक्रनाइज़ बनाने तक पहुँचने myVar
0

AFAIK के लिए, आधुनिक JVMs नहीं रह विभाजित लंबे और डबल ऑपरेशन। मुझे किसी भी संदर्भ के बारे में पता नहीं है जो कहता है कि यह अभी भी एक समस्या है। उदाहरण के लिए, एटमिक लोंग देखें जो सूर्य के जेवीएम में सिंक्रनाइज़ेशन का उपयोग नहीं करता है।

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

हालांकि, एक बेहतर समाधान एक परमाणु लांग का उपयोग करना है। यह परमाणु संचालन का समर्थन करता है जैसे मिलता है, सेट और जोड़ता है और सिंक्रनाइज़ेशन का उपयोग नहीं करता है।

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