Java सिंक्रोनाइज़्ड ब्लॉक बनाम कलेक्शंस। SynchronizedMap


85

निम्नलिखित कोड को कॉल को सही ढंग से सिंक्रनाइज़ करने के लिए सेट किया गया है synchronizedMap?

public class MyClass {
  private static Map<String, List<String>> synchronizedMap = Collections.synchronizedMap(new HashMap<String, List<String>>());

  public void doWork(String key) {
    List<String> values = null;
    while ((values = synchronizedMap.remove(key)) != null) {
      //do something with values
    }
  }

  public static void addToMap(String key, String value) {
    synchronized (synchronizedMap) {
      if (synchronizedMap.containsKey(key)) {
        synchronizedMap.get(key).add(value);
      }
      else {
        List<String> valuesList = new ArrayList<String>();
        valuesList.add(value);
        synchronizedMap.put(key, valuesList);
      }
    }
  }
}

मेरी समझ से, मुझे addToMap()किसी अन्य थ्रेड को कॉल करने remove()से containsKey()पहले या कॉल के माध्यम से प्राप्त करने से पहले put()सिंक्रनाइज़ किए गए ब्लॉक की आवश्यकता है, लेकिन मुझे एक सिंक्रनाइज़ किए गए ब्लॉक की आवश्यकता नहीं है doWork()क्योंकि एक और थ्रेड रिटर्न addToMap()से पहले सिंक्रनाइज़ किए गए ब्लॉक में प्रवेश नहीं कर सकता remove()क्योंकि मैंने मूल रूप से मैप बनाया था के साथ Collections.synchronizedMap()। क्या वो सही है? क्या ऐसा करने के लिए इससे अच्छा तरीका है?

जवाबों:


90

Collections.synchronizedMap() इस बात की गारंटी देता है कि मानचित्र पर आप जिस प्रत्येक परमाणु संचालन को चलाना चाहते हैं, वह समकालिक होगा।

हालांकि, नक्शे पर दो (या अधिक) संचालन चलाना एक ब्लॉक में सिंक्रनाइज़ किया जाना चाहिए। तो हाँ - आप सही ढंग से सिंक्रनाइज़ कर रहे हैं।


26
मुझे लगता है कि यह उल्लेख करना अच्छा होगा कि यह काम करता है क्योंकि javadocs स्पष्ट रूप से बताती है कि सिंक्रोनाइज़ेशन नक्शे पर ही सिंक्रनाइज़ होता है, न कि कुछ आंतरिक लॉक। अगर ऐसा होता तो समकालिक (सिंक्रोनाइज़ेशन) सही नहीं होता।
एक्सट्रोनॉन

2
@ क्या आप अपने जवाब को थोड़ा और गहराई से बता सकते हैं? आप कहते हैं कि sychronizedMap परमाणु संचालन करता है, लेकिन तब आपको अपने स्वयं के सिंक्रनाइज़ किए गए ब्लॉक की आवश्यकता क्यों होगी यदि सिंकपॉइंट ने आपके सभी ऑपरेशनों को परमाणु बना दिया है? आपका पहला पैराग्राफ दूसरे के बारे में चिंता करने को रोकता है।
अलमेल

@almel मेरा जवाब
सर्गेई

2
मानचित्र को पहले से ही उपयोग किए जाने वाले ब्लॉक को सिंक्रनाइज़ करना क्यों आवश्यक है Collections.synchronizedMap()? मुझे दूसरा बिंदु नहीं मिल रहा है।
बिमल शर्मा


13

की क्षमता हैआपके कोड में एक सूक्ष्म बग ।

[ अद्यतन: जब से वह map.remove () का उपयोग कर रहा है यह विवरण पूरी तरह से मान्य नहीं है। मुझे वह तथ्य याद आ गया। :( प्रश्न के लेखक के लिए धन्यवाद कि इसे इंगित करने के लिए। मैं बाकी को छोड़ रहा हूं, लेकिन मुख्य कथन को बदलने के लिए कहा जा सकता है कि संभवतः एक बग है।]

में doWork () आप एक धागा सुरक्षित तरीका में मानचित्र से सूची मूल्य मिलता है। हालांकि, बाद में, आप उस सूची को असुरक्षित मामले में एक्सेस कर रहे हैं। उदाहरण के लिए, एक थ्रेड doWork () में सूची का उपयोग कर रहा हो सकता है, जबकि एक अन्य धागा सिंक्रोनाइज़ (गैजेट) (कुंजी) .add (मान) को addToMap () में शामिल करता है। । उन दोनों का उपयोग सिंक्रनाइज़ नहीं है। अंगूठे का नियम यह है कि संग्रह की थ्रेड-सुरक्षित गारंटी उन कुंजियों या मूल्यों तक नहीं है जो वे संग्रहीत करते हैं।

आप नक्शे में एक सिंक्रनाइज़ सूची सम्मिलित करके इसे ठीक कर सकते हैं

List<String> valuesList = new ArrayList<String>();
valuesList.add(value);
synchronizedMap.put(key, Collections.synchronizedList(valuesList)); // sync'd list

वैकल्पिक रूप से आप doWork () में सूची का उपयोग करते समय मानचित्र पर सिंक्रनाइज़ कर सकते हैं :

  public void doWork(String key) {
    List<String> values = null;
    while ((values = synchronizedMap.remove(key)) != null) {
      synchronized (synchronizedMap) {
          //do something with values
      }
    }
  }

अंतिम विकल्प संक्षिप्तता को थोड़ा सीमित करेगा, लेकिन कुछ हद तक स्पष्ट IMO है।

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

यह वर्ग उन कार्यक्रमों में हैशटेबल के साथ पूरी तरह से इंटरऑपरेबल है जो इसके थ्रेड सुरक्षा पर निर्भर करते हैं लेकिन इसके सिंक्रनाइज़ेशन विवरण पर नहीं

दूसरे शब्दों में, putIfAbsent () परमाणु आवेषण के लिए बहुत अच्छा है, लेकिन यह गारंटी नहीं देता है कि इस कॉल के दौरान मानचित्र के अन्य हिस्से नहीं बदलेंगे; यह केवल परमाणु की गारंटी देता है। अपने नमूना कार्यक्रम में, आप पुट (एस) के अलावा अन्य चीजों के लिए (एक सिंक्रनाइज़) हाशप के सिंक्रनाइज़ेशन विवरण पर भरोसा कर रहे हैं।

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

प्रत्येक परिवर्तनशील राज्य चर के लिए जिसे एक से अधिक थ्रेड द्वारा एक्सेस किया जा सकता है, उस वेरिएबल तक सभी एक्सेस को उसी लॉक के साथ किया जाना चाहिए।


मैं बग के बारे में आपकी बात देखता हूं यदि मैं सिंक्रोनाइज़.गेट () के साथ सूची तक पहुंच रहा था। जब से मैं हटाने () का उपयोग कर रहा हूं, क्या उस कुंजी के साथ अगला ऐड नहीं होना चाहिए, एक नया एरेलेलिस्ट बनाएं और उस एक के साथ हस्तक्षेप न करें जिसे मैं doWork में उपयोग कर रहा हूं?
रयान आहर्न

सही बात! मैंने आपके निष्कासन को पूरी तरह से समाप्त कर दिया है।
JLR

1
प्रत्येक परिवर्तनशील राज्य चर के लिए जिसे एक से अधिक थ्रेड द्वारा एक्सेस किया जा सकता है, उस वेरिएबल तक सभी एक्सेस को उसी लॉक के साथ किया जाना चाहिए। ---- मैं आम तौर पर एक निजी संपत्ति जोड़ देता हूं बस एक नई वस्तु () का उपयोग करें और मेरे तुल्यकालन ब्लॉकों के लिए उपयोग करें। इस तरह मैं उस संदर्भ के लिए कच्चे के माध्यम से अपने सभी को जानता हूं। सिंक्रोनाइज़्ड (objectInVar) {}
एंथनीजिंक

11

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

synchronized (synchronizedMap) {
    if (synchronizedMap.containsKey(key)) {
        synchronizedMap.get(key).add(value);
    }
    else {
        List<String> valuesList = new ArrayList<String>();
        valuesList.add(value);
        synchronizedMap.put(key, valuesList);
    }
}

इस कोड में

synchronizedMap.get(key).add(value);

तथा

synchronizedMap.put(key, valuesList);

विधि कॉल पिछले के परिणाम पर निर्भर हैं

synchronizedMap.containsKey(key)

विधि कॉल।

यदि विधि कॉल के अनुक्रम को सिंक्रनाइज़ नहीं किया गया तो परिणाम गलत हो सकता है। उदाहरण के लिए thread 1विधि निष्पादित कर रहा है addToMap()और विधि thread 2निष्पादित कर रहा है doWork()synchronizedMapऑब्जेक्ट पर विधि कॉल का क्रम निम्नानुसार हो सकता है: Thread 1विधि निष्पादित की गई है

synchronizedMap.containsKey(key)

और परिणाम " true" है। उसके बाद ऑपरेटिंग सिस्टम ने निष्पादन नियंत्रण को बदल दिया है thread 2और इसे निष्पादित किया है

synchronizedMap.remove(key)

उसके बाद निष्पादन नियंत्रण को वापस स्विच किया गया है thread 1और इसे उदाहरण के लिए निष्पादित किया गया है

synchronizedMap.get(key).add(value);

विश्वास करने वाली synchronizedMapवस्तु में समाहित है keyऔर NullPointerExceptionफेंक दिया synchronizedMap.get(key) जाएगा क्योंकि वापस आ जाएगी null। यदि synchronizedMapऑब्जेक्ट पर विधि कॉल का क्रम एक-दूसरे के परिणामों पर निर्भर नहीं है, तो आपको अनुक्रम को सिंक्रनाइज़ करने की आवश्यकता नहीं है। उदाहरण के लिए आपको इस अनुक्रम को सिंक्रनाइज़ करने की आवश्यकता नहीं है:

synchronizedMap.put(key1, valuesList1);
synchronizedMap.put(key2, valuesList2);

यहाँ

synchronizedMap.put(key2, valuesList2);

मेथड कॉल पिछले के परिणामों पर निर्भर नहीं करता है

synchronizedMap.put(key1, valuesList1);

मेथड कॉल (यह ध्यान नहीं देता है कि दो विधि कॉलों के बीच कुछ धागे ने हस्तक्षेप किया है और उदाहरण के लिए हटा दिया है key1)।


4

यह मुझे सही लगता है। अगर मुझे कुछ भी बदलना था, तो मैं Collections.synchronizedMap () का उपयोग करना बंद कर दूंगा और सबकुछ उसी तरह से सिंक्रोनाइज़ करूंगा, बस इसे स्पष्ट करने के लिए।

इसके अलावा, मैं प्रतिस्थापित करूंगा

  if (synchronizedMap.containsKey(key)) {
    synchronizedMap.get(key).add(value);
  }
  else {
    List<String> valuesList = new ArrayList<String>();
    valuesList.add(value);
    synchronizedMap.put(key, valuesList);
  }

साथ में

List<String> valuesList = synchronziedMap.get(key);
if (valuesList == null)
{
  valuesList = new ArrayList<String>();
  synchronziedMap.put(key, valuesList);
}
valuesList.add(value);

3
करने की चीज। मुझे यह नहीं मिलता है कि क्यों हमें Collections.synchronizedXXX()एपीआई का उपयोग तब करना चाहिए जब हमें अभी भी किसी वस्तु पर सिंक्रनाइज़ करना है (जो कि ज्यादातर मामलों में केवल संग्रह ही होगा) हमारे हर दिन ऐप के तर्क में
kellogs

3

आपके द्वारा सिंक्रनाइज़ किया गया तरीका सही है। लेकिन वहां एक जाल है

  1. संग्रह ढांचे द्वारा प्रदान किया गया सिंक्रोनाइज़्ड रैपर यह सुनिश्चित करता है कि विधि Ie ऐड / गेट / कॉल्स को परस्पर अनन्य रूप से चलाएगी।

हालांकि वास्तविक दुनिया में आप आमतौर पर मूल्य में डालने से पहले नक्शे को क्वेरी करेंगे। इसलिए आपको दो ऑपरेशन करने की आवश्यकता होगी और इसलिए एक सिंक्रनाइज़ ब्लॉक की आवश्यकता है। तो जिस तरह से आपने इसका इस्तेमाल किया है वह सही है। तथापि।

  1. आप संग्रह ढांचे में उपलब्ध मानचित्र के समवर्ती कार्यान्वयन का उपयोग कर सकते थे। 'समवर्ती हाशपा' लाभ है

ए। इसमें एक API 'putIfAbsent' है जो समान सामान तो करेगा लेकिन अधिक कुशल तरीके से।

ख। इसका कुशल: d CococonMap केवल कुंजी को लॉक करता है इसलिए यह पूरे मानचित्र की दुनिया को अवरुद्ध नहीं करता है। जहाँ आपने कुंजियों के साथ-साथ मूल्यों को भी अवरुद्ध कर दिया है।

सी। आप अपने कोडबेस में कहीं और अपनी मैप ऑब्जेक्ट के संदर्भ को पास कर सकते हैं, जहाँ आप / आपके टीन के अन्य देव गलत तरीके से उपयोग कर सकते हैं। यानी वह नक्शे के ऑब्जेक्ट पर लॉक किए बिना सभी जोड़ () या प्राप्त () कर सकता है। इसलिए उनका कॉल आपके सिंक ब्लॉक में पारस्परिक रूप से अनन्य नहीं होगा। लेकिन समवर्ती कार्यान्वयन का उपयोग करने से आपको मानसिक शांति मिलती है कि इसका उपयोग कभी भी गलत तरीके से नहीं किया जा सकता है।


2

की जाँच करें गूगल संग्रह ' Multimap, जैसे की पेज 28 इस प्रस्तुति

यदि आप किसी कारण से उस लाइब्रेरी का उपयोग नहीं कर सकते हैं, तो ConcurrentHashMapइसके बजाय का उपयोग करने पर विचार करें SynchronizedHashMap; यह एक निफ्टी putIfAbsent(K,V)विधि है जिसके साथ आप तत्व सूची को जोड़ सकते हैं यदि यह पहले से ही नहीं है। इसके अलावा, CopyOnWriteArrayListयदि आपके उपयोग पैटर्न ऐसा करने का वारंट करते हैं, तो मानचित्र मानों का उपयोग करने पर विचार करें।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.