2012-08-27 17 views
9

मैं Map को int मानों के साथ बनाने और कई धागे से उन्हें बढ़ाने की कोशिश कर रहा हूं। दो या दो से अधिक धागे एक ही कुंजी को बढ़ा सकते हैं।जावा मानचित्र समवर्ती अद्यतन

ConcurrentHashMap प्रलेखन मेरे लिए बहुत स्पष्ट नहीं था, क्योंकि यह sais कि:

Retrieval operations (including get) generally do not block, so may overlap with update operations (including put and remove)

मुझे आश्चर्य है अगर निम्न कोड ConcurrentHashMap इच्छा का उपयोग कर काम करता है सही ढंग से:

myMap.put(X, myMap.get(X) + 1);

नहीं तो , मैं ऐसी चीज कैसे प्रबंधित कर सकता हूं?

+0

जावा 8 में, यह सुरक्षित रूप से 'myMap.merge (एक्स, 1, पूर्णांक के साथ किया जा सकता है: : योग) '। – shmosel

उत्तर

9

समवर्ती नक्शा आपके कोड की थ्रेड सुरक्षा में मदद नहीं करेगा। आप अभी भी दौड़ की स्थिति प्राप्त कर सकते हैं:

Thread-1: x = 1, get(x) 
Thread-2: x = 1, get(x) 
Thread-1: put(x + 1) => 2 
Thread-2: put(x + 1) => 2 

दो वृद्धि हुई, लेकिन आपको अभी भी केवल +1 मिल गया है। आपको केवल एक समवर्ती मानचित्र की आवश्यकता है यदि आप मानचित्र को संशोधित करना चाहते हैं, न कि इसकी सामग्री। यहां तक ​​कि सबसे सरल HashMapthreadsafe for concurrent reads है, नक्शा अब और अधिक उत्परिवर्तित नहीं है।

तो आदिम प्रकार के लिए थ्रेडसेफ मानचित्र की बजाय, आपको प्रकार के लिए थ्रेडसेफ रैपर की आवश्यकता है। java.util.concurrent.atomic से कुछ या मनमाने ढंग से टाइप करने की आवश्यकता होने पर अपना खुद का लॉक कंटेनर रोल करें।

1

आप ऑपरेशन को synchronized (myMap) {...} ब्लॉक में डाल सकते हैं।

3

एक विचार परमाणु इंटेगर के साथ ConcurrentMap का संयोजन होगा, जिसमें एक वृद्धि विधि है।

AtomicInteger current = map.putIfAbsent(key, new AtomicInteger(1)); 
int newValue = current == null ? 1 :current.incrementAndGet(); 

या (और अधिक कुशलता से, धन्यवाद @Keppil) एक अतिरिक्त कोड गार्ड के साथ अनावश्यक वस्तु निर्माण से बचने के लिए:

AtomicInteger current = map.get(key); 
if (current == null){ 
    current = map.putIfAbsent(key, new AtomicInteger(1)); 
} 
int newValue = current == null ? 1 : current.incrementAndGet(); 
+0

यदि आप एक ConcurrentMap का उपयोग करते हैं तो परमाणु पूर्णांक का उपयोग करने में कोई बात नहीं है, इसके लिए ConcurrentHashMap.replace (K, V, V) बनाया गया है। – jolivier

+0

यदि दुर्लभ अपडेट होते हैं, तो @dflemstr सुझावों की तरह एक सरल सिंक्रनाइज़ भी काम करेगा। सुनिश्चित नहीं है कि आपको AtomicInteger को न्यायसंगत बनाने के लिए कितना थ्रूपुट चाहिए। – Thilo

+1

@ जोलिवियर 'प्रतिस्थापन' पुनः प्रयास करने के अधीन है, जबकि 'getAndIncrement' नहीं है। –

0

आपके मौजूदा कोड अपने नक्शे के मूल्यों में परिवर्तन समवर्ती तो यह काम नहीं करेगा।

यदि एकाधिक धागे आपके मानचित्र में put मान डाल सकते हैं, तो आपको समवर्ती मानचित्र जैसे ConcurrentHashMap का उपयोग गैर थ्रेड सुरक्षित मानों जैसे Integer के साथ करना होगा। ConcurrentMap.replace तब आप जो चाहते हैं वह करेंगे (या अपने कोड को कम करने के लिए AtomicInteger का उपयोग करें)।

यदि आपका धागे केवल मूल्यों को बदल जाएगा (और नहीं जोड़/कुंजी बदल) अपने नक्शे के, तो आप एक मानक नक्शा भंडारण धागा सुरक्षित मानAtomicInteger की तरह उपयोग कर सकते हैं। फिर आपका धागा कॉल करेगा: उदाहरण के लिए map.get(key).incrementAndGet()

2

सर्वोत्तम अभ्यास। आप हैश मैप और परमाणु इंटेगर का उपयोग कर सकते हैं। टेस्ट कोड:

public class HashMapAtomicIntegerTest { 
    public static final int KEY = 10; 

    public static void main(String[] args) { 
     HashMap<Integer, AtomicInteger> concurrentHashMap = new HashMap<Integer, AtomicInteger>(); 
     concurrentHashMap.put(HashMapAtomicIntegerTest.KEY, new AtomicInteger()); 
     List<HashMapAtomicCountThread> threadList = new ArrayList<HashMapAtomicCountThread>(); 
     for (int i = 0; i < 500; i++) { 
      HashMapAtomicCountThread testThread = new HashMapAtomicCountThread(
        concurrentHashMap); 
      testThread.start(); 
      threadList.add(testThread); 
     } 
     int index = 0; 
     while (true) { 
      for (int i = index; i < 500; i++) { 
       HashMapAtomicCountThread testThread = threadList.get(i); 
       if (testThread.isAlive()) { 
        break; 
       } else { 
        index++; 
       } 
      } 
      if (index == 500) { 
       break; 
      } 
     } 
     System.out.println("The result value should be " + 5000000 
       + ",actually is" 
       + concurrentHashMap.get(HashMapAtomicIntegerTest.KEY)); 
    } 
} 

class HashMapAtomicCountThread extends Thread { 
    HashMap<Integer, AtomicInteger> concurrentHashMap = null; 

    public HashMapAtomicCountThread(
      HashMap<Integer, AtomicInteger> concurrentHashMap) { 
     this.concurrentHashMap = concurrentHashMap; 
    } 

    @Override 
    public void run() { 
     for (int i = 0; i < 10000; i++) { 
      concurrentHashMap.get(HashMapAtomicIntegerTest.KEY) 
        .getAndIncrement(); 
     } 
    } 
} 

परिणाम:

परिणाम मूल्य 5000000 होना चाहिए, वास्तव में is5000000

या HashMap और सिंक्रनाइज़ है, लेकिन से पूर्व

public class HashMapSynchronizeTest { 

    public static final int KEY = 10; 

    public static void main(String[] args) { 

     HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>(); 
     hashMap.put(KEY, 0); 
     List<HashMapSynchronizeThread> threadList = new ArrayList<HashMapSynchronizeThread>(); 
     for (int i = 0; i < 500; i++) { 
      HashMapSynchronizeThread testThread = new HashMapSynchronizeThread(
        hashMap); 
      testThread.start(); 
      threadList.add(testThread); 
     } 
     int index = 0; 
     while (true) { 
      for (int i = index; i < 500; i++) { 
       HashMapSynchronizeThread testThread = threadList.get(i); 
       if (testThread.isAlive()) { 
        break; 
       } else { 
        index++; 
       } 
      } 
      if (index == 500) { 
       break; 
      } 
     } 
     System.out.println("The result value should be " + 5000000 
       + ",actually is" + hashMap.get(KEY)); 
    } 
} 

class HashMapSynchronizeThread extends Thread { 
    HashMap<Integer, Integer> hashMap = null; 

    public HashMapSynchronizeThread(
      HashMap<Integer, Integer> hashMap) { 
     this.hashMap = hashMap; 
    } 

    @Override 
    public void run() { 
     for (int i = 0; i < 10000; i++) { 
      synchronized (hashMap) { 
       hashMap.put(HashMapSynchronizeTest.KEY, 
         hashMap 
           .get(HashMapSynchronizeTest.KEY) + 1); 
      } 
     } 
    } 
} 
बहुत धीमी

परिणाम:

परिणाम मूल्य 5000000 होना चाहिए, वास्तव में is5000000

उपयोग ConcurrentHashMap गलत परिणाम मिलेंगे।

public class ConcurrentHashMapTest { 

    public static final int KEY = 10; 

    public static void main(String[] args) { 
     ConcurrentHashMap<Integer, Integer> concurrentHashMap = new ConcurrentHashMap<Integer, Integer>(); 
     concurrentHashMap.put(KEY, 0); 
     List<CountThread> threadList = new ArrayList<CountThread>(); 
     for (int i = 0; i < 500; i++) { 
      CountThread testThread = new CountThread(concurrentHashMap); 
      testThread.start(); 
      threadList.add(testThread); 
     } 
     int index = 0; 
     while (true) { 
      for (int i = index; i < 500; i++) { 
       CountThread testThread = threadList.get(i); 
       if (testThread.isAlive()) { 
        break; 
       } else { 
        index++; 
       } 
      } 
      if (index == 500) { 
       break; 
      } 
     } 
     System.out.println("The result value should be " + 5000000 
       + ",actually is" + concurrentHashMap.get(KEY)); 
    } 
} 

class CountThread extends Thread { 
    ConcurrentHashMap<Integer, Integer> concurrentHashMap = null; 

    public CountThread(ConcurrentHashMap<Integer, Integer> concurrentHashMap) { 
     this.concurrentHashMap = concurrentHashMap; 
    } 

    @Override 
    public void run() { 
     for (int i = 0; i < 10000; i++) { 
      concurrentHashMap.put(ConcurrentHashMapTest.KEY, 
        concurrentHashMap.get(ConcurrentHashMapTest.KEY) + 1); 
     } 
    } 
} 

परिणाम:

परिणाम मूल्य 5000000 होना चाहिए, वास्तव में is11759

+0

आप @ vtmarvin के उत्तर से सिद्धांत सीख सकते हैं – wodong