क्या समवर्तीहैश मैप मूल्यों को पुनरावृत्त करना सुरक्षित है?


156

समवर्ती हाशपा के लिए javadoc में निम्नलिखित है:

पुनर्प्राप्ति संचालन (प्राप्त करें सहित) आम तौर पर ब्लॉक नहीं होता है, इसलिए अपडेट संचालन (पुट और निकालें सहित) के साथ ओवरलैप हो सकता है। पुनर्प्राप्ति सबसे हाल ही में अपडेट किए गए अपडेट संचालन के परिणामों को दर्शाती है जो उनकी शुरुआत में होती हैं। PutAll और स्पष्ट जैसे कुल संचालन के लिए, समवर्ती पुनर्प्राप्ति केवल कुछ प्रविष्टियों के सम्मिलन या हटाने को दर्शा सकती है। इसी तरह, Iterators और Enumerations कुछ तत्वों पर हैश तालिका की स्थिति को दर्शाते हुए या इट्रेटर / एन्यूमरेशन के निर्माण के बाद से लौटते हैं। वे ConcurrentModificationException को नहीं फेंकते हैं। हालांकि, पुनरावृत्तियों को एक समय में केवल एक थ्रेड द्वारा उपयोग करने के लिए डिज़ाइन किया गया है।

इसका क्या मतलब है? यदि मैं एक ही समय में दो थ्रेड्स के साथ मानचित्र को पुनरावृत्त करने की कोशिश करता हूं तो क्या होगा? यदि मैं इसे पुनरावृत्त करते हुए मानचित्र से मान निकालता या हटाता हूं तो क्या होगा?

जवाबों:


193

इसका क्या मतलब है?

इसका मतलब है कि आपके द्वारा प्राप्त प्रत्येक पुनरावृत्ति ConcurrentHashMapएक एकल धागे द्वारा उपयोग किए जाने के लिए डिज़ाइन किया गया है और इसे पास नहीं होना चाहिए। इसमें सिंटैक्टिक शुगर शामिल है जो प्रत्येक लूप प्रदान करता है।

यदि मैं एक ही समय में दो थ्रेड्स के साथ मैप को पुनरावृत्त करने की कोशिश करता हूं तो क्या होगा?

यह अपेक्षा के अनुसार काम करेगा यदि प्रत्येक थ्रेड का उपयोग करता है यह स्वयं का इटेटर है।

यदि मैं इसे पुनरावृत्त करते हुए मानचित्र से मान निकालता या हटाता हूं तो क्या होगा?

यह गारंटी है कि अगर आप ऐसा करते हैं तो चीजें नहीं टूटेंगी ( ConcurrentHashMapइसका मतलब है कि "समवर्ती" का मतलब है)। हालाँकि, इस बात की कोई गारंटी नहीं है कि एक थ्रेड मैप में उन परिवर्तनों को देखेगा, जो दूसरा थ्रेड निष्पादित करता है (मैप से नया पुनरावृत्ति प्राप्त किए बिना)। निर्माण के समय नक्शे की स्थिति को दर्शाने के लिए पुनरावृति की गारंटी दी जाती है। फ्यूचर परिवर्तन इटैलर में परिलक्षित हो सकते हैं, लेकिन उनका होना आवश्यक नहीं है।

अंत में, एक बयान की तरह

for (Object o : someConcurrentHashMap.entrySet()) {
    // ...
}

ठीक है (या कम से कम सुरक्षित) लगभग हर बार जब आप इसे देखेंगे।


तो क्या होगा यदि पुनरावृत्ति के दौरान, किसी अन्य थ्रेड ने ऑब्जेक्ट ओ 10 को मानचित्र से हटा दिया है? क्या मुझे अभी भी पुनरावृत्ति में ओ 10 दिखाई दे सकता है, भले ही उसे हटा दिया गया हो? @Waldheinz
एलेक्स

जैसा कि ऊपर कहा गया है, यह वास्तव में निर्दिष्ट नहीं है यदि कोई मौजूदा पुनरावृत्त मानचित्र में बाद के परिवर्तनों को प्रतिबिंबित करेगा। इसलिए मुझे पता नहीं है, और विनिर्देश द्वारा कोई भी (कोड को देखे बिना, और वह रनटाइम के हर अपडेट के साथ बदल सकता है) नहीं करता है। इसलिए आप इस पर भरोसा नहीं कर सकते।
8

8
लेकिन मैं अभी भी एक ConcurrentModificationExceptioniterating समय मिल गया ConcurrentHashMap, क्यों?
किमी चिउ

@KimiChiu आपको शायद एक नया प्रश्न पोस्ट करना चाहिए जो उस अपवाद को ट्रिगर करने वाला कोड प्रदान करता है, लेकिन मुझे अत्यधिक संदेह है कि यह सीधे सीधे एक समवर्ती कंटेनर से उपजी है। जब तक जावा कार्यान्वयन छोटी गाड़ी नहीं है।
वाल्डहिनज़

18

आप इस क्लास का उपयोग दो एक्सेसिंग थ्रेड्स को टेस्ट करने के लिए कर सकते हैं और एक साझा उदाहरण को म्यूट कर सकते हैं ConcurrentHashMap:

import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConcurrentMapIteration
{
  private final Map<String, String> map = new ConcurrentHashMap<String, String>();

  private final static int MAP_SIZE = 100000;

  public static void main(String[] args)
  {
    new ConcurrentMapIteration().run();
  }

  public ConcurrentMapIteration()
  {
    for (int i = 0; i < MAP_SIZE; i++)
    {
      map.put("key" + i, UUID.randomUUID().toString());
    }
  }

  private final ExecutorService executor = Executors.newCachedThreadPool();

  private final class Accessor implements Runnable
  {
    private final Map<String, String> map;

    public Accessor(Map<String, String> map)
    {
      this.map = map;
    }

    @Override
    public void run()
    {
      for (Map.Entry<String, String> entry : this.map.entrySet())
      {
        System.out.println(
            Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']'
        );
      }
    }
  }

  private final class Mutator implements Runnable
  {

    private final Map<String, String> map;
    private final Random random = new Random();

    public Mutator(Map<String, String> map)
    {
      this.map = map;
    }

    @Override
    public void run()
    {
      for (int i = 0; i < 100; i++)
      {
        this.map.remove("key" + random.nextInt(MAP_SIZE));
        this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString());
        System.out.println(Thread.currentThread().getName() + ": " + i);
      }
    }
  }

  private void run()
  {
    Accessor a1 = new Accessor(this.map);
    Accessor a2 = new Accessor(this.map);
    Mutator m = new Mutator(this.map);

    executor.execute(a1);
    executor.execute(m);
    executor.execute(a2);
  }
}

कोई अपवाद नहीं फेंका जाएगा।

एक्सेसर थ्रेड्स के बीच समान पुनरावृत्ति साझा करने से गतिरोध उत्पन्न हो सकता है:

import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConcurrentMapIteration
{
  private final Map<String, String> map = new ConcurrentHashMap<String, String>();
  private final Iterator<Map.Entry<String, String>> iterator;

  private final static int MAP_SIZE = 100000;

  public static void main(String[] args)
  {
    new ConcurrentMapIteration().run();
  }

  public ConcurrentMapIteration()
  {
    for (int i = 0; i < MAP_SIZE; i++)
    {
      map.put("key" + i, UUID.randomUUID().toString());
    }
    this.iterator = this.map.entrySet().iterator();
  }

  private final ExecutorService executor = Executors.newCachedThreadPool();

  private final class Accessor implements Runnable
  {
    private final Iterator<Map.Entry<String, String>> iterator;

    public Accessor(Iterator<Map.Entry<String, String>> iterator)
    {
      this.iterator = iterator;
    }

    @Override
    public void run()
    {
      while(iterator.hasNext()) {
        Map.Entry<String, String> entry = iterator.next();
        try
        {
          String st = Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']';
        } catch (Exception e)
        {
          e.printStackTrace();
        }

      }
    }
  }

  private final class Mutator implements Runnable
  {

    private final Map<String, String> map;
    private final Random random = new Random();

    public Mutator(Map<String, String> map)
    {
      this.map = map;
    }

    @Override
    public void run()
    {
      for (int i = 0; i < 100; i++)
      {
        this.map.remove("key" + random.nextInt(MAP_SIZE));
        this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString());
      }
    }
  }

  private void run()
  {
    Accessor a1 = new Accessor(this.iterator);
    Accessor a2 = new Accessor(this.iterator);
    Mutator m = new Mutator(this.map);

    executor.execute(a1);
    executor.execute(m);
    executor.execute(a2);
  }
}

जैसे ही आप Iterator<Map.Entry<String, String>>एक्सेसर और म्यूटेटर थ्रेड्स के बीच एक ही साझा करना शुरू करते हैं, java.lang.IllegalStateExceptionपॉप अप शुरू हो जाएगा।

import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConcurrentMapIteration
{
  private final Map<String, String> map = new ConcurrentHashMap<String, String>();
  private final Iterator<Map.Entry<String, String>> iterator;

  private final static int MAP_SIZE = 100000;

  public static void main(String[] args)
  {
    new ConcurrentMapIteration().run();
  }

  public ConcurrentMapIteration()
  {
    for (int i = 0; i < MAP_SIZE; i++)
    {
      map.put("key" + i, UUID.randomUUID().toString());
    }
    this.iterator = this.map.entrySet().iterator();
  }

  private final ExecutorService executor = Executors.newCachedThreadPool();

  private final class Accessor implements Runnable
  {
    private final Iterator<Map.Entry<String, String>> iterator;

    public Accessor(Iterator<Map.Entry<String, String>> iterator)
    {
      this.iterator = iterator;
    }

    @Override
    public void run()
    {
      while (iterator.hasNext())
      {
        Map.Entry<String, String> entry = iterator.next();
        try
        {
          String st =
              Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']';
        } catch (Exception e)
        {
          e.printStackTrace();
        }

      }
    }
  }

  private final class Mutator implements Runnable
  {

    private final Random random = new Random();

    private final Iterator<Map.Entry<String, String>> iterator;

    private final Map<String, String> map;

    public Mutator(Map<String, String> map, Iterator<Map.Entry<String, String>> iterator)
    {
      this.map = map;
      this.iterator = iterator;
    }

    @Override
    public void run()
    {
      while (iterator.hasNext())
      {
        try
        {
          iterator.remove();
          this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString());
        } catch (Exception ex)
        {
          ex.printStackTrace();
        }
      }

    }
  }

  private void run()
  {
    Accessor a1 = new Accessor(this.iterator);
    Accessor a2 = new Accessor(this.iterator);
    Mutator m = new Mutator(map, this.iterator);

    executor.execute(a1);
    executor.execute(m);
    executor.execute(a2);
  }
}

क्या आप इस बारे में निश्चित हैं कि 'एक्सेसर थ्रेड्स के बीच समान इटरेटर साझा करना गतिरोध पैदा कर सकता है'? दस्तावेज़ कहता है कि पढ़ा गया ब्लॉक नहीं किया गया है और मैंने आपके कार्यक्रम की कोशिश की है और अभी तक कोई गतिरोध नहीं हुआ है। यद्यपि पुनरावृति परिणाम गलत होगा।
टोनी

12

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


किसी भी कारण से आपने I को Iterator में कैपिटल नहीं किया है? चूंकि यह कक्षा का नाम है, इसलिए यह कम भ्रमित हो सकता है।
बिल माइकेल

1
@ बिल माइकेल, अब हम शिष्टाचार पोस्ट करने के शब्दार्थ में हैं। मुझे लगता है कि उन्हें Iterator को Iterator के लिए javadoc पर वापस लिंक करना चाहिए था, या बहुत कम से कम इसे इनलाइन कोड एनोटेशन (`) के अंदर रखा था।
टिम बेंडर

10

इससे आपको अच्छी जानकारी मिल सकती है

ConcurrentHashMap कॉल करने वालों से किए गए वादों को थोड़ा आराम करके उच्च संगामिति प्राप्त करता है। एक पुनर्प्राप्ति ऑपरेशन सबसे हाल ही में सम्मिलित सम्मिलित ऑपरेशन द्वारा डाले गए मूल्य को वापस करेगा, और एक सम्मिलन ऑपरेशन द्वारा जोड़े गए मूल्य को भी वापस कर सकता है जो समवर्ती रूप से प्रगति पर है (लेकिन किसी भी स्थिति में यह एक बकवास परिणाम नहीं लौटाएगा)। Iterators समसामयिक HashMap.iterator () द्वारा लौटाए गए प्रत्येक तत्व को एक बार में लौटाएगा और कभी भी समवर्ती नहीं करेगा।। संग्रह को पुनरावृत्त करते समय थ्रेड-सुरक्षा प्रदान करने के लिए किसी तालिका-वाइड लॉकिंग की आवश्यकता नहीं है (या यहां तक ​​कि संभव है)। ConcurrentHashMap किसी भी अनुप्रयोग में सिंक्रोनाइज़ेशन या हैशटेबल के प्रतिस्थापन के रूप में उपयोग किया जा सकता है जो अपडेट को रोकने के लिए पूरी तालिका को लॉक करने की क्षमता पर भरोसा नहीं करता है।

इस से सम्बन्धित:

हालांकि, पुनरावृत्तियों को एक समय में केवल एक थ्रेड द्वारा उपयोग करने के लिए डिज़ाइन किया गया है।

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


4

इसका क्या मतलब है?

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

यदि मैं एक ही समय में दो थ्रेड्स के साथ मानचित्र को पुनरावृत्त करने की कोशिश करता हूं तो क्या होगा?

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

लेकिन अगर दो धागे अलग-अलग पुनरावृत्तियों का उपयोग करते हैं, तो आपको ठीक होना चाहिए।

यदि मैं इसे पुनरावृत्त करते हुए मानचित्र से मान निकालता या हटाता हूं तो क्या होगा?

यह एक अलग मुद्दा है, लेकिन आपके द्वारा पर्याप्त रूप से उद्धृत javadoc अनुभाग इसका उत्तर देता है। मूल रूप से, पुनरावृत्तियाँ थ्रेड-सुरक्षित हैं, लेकिन यह परिभाषित नहीं है कि क्या आप पुनरावृत्त द्वारा लौटाए गए ऑब्जेक्ट्स के अनुक्रम में प्रतिबिंबित किसी भी समवर्ती सम्मिलन, अपडेट या विलोपन के प्रभाव देखेंगे। व्यवहार में, यह संभवत: इस बात पर निर्भर करता है कि नक्शे में कहां अपडेट होते हैं।

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