2012-04-23 16 views
15

के साथ बदलें मैं एक USECASE जहाँ मैंputIfAbsent गठबंधन और ConcurrentMap

करने के लिए है
  • एक नया मान सम्मिलित करता है, तो कुंजी ConcurrentHashMap
  • में मौजूद नहीं है एक नया मान यदि कुंजी के साथ पुराने मूल्य की जगह है पहले से ही ConcurrentHashMap, जहां नई मूल्य पुराने मूल्य (नहीं एक महंगी आपरेशन)

से ली गई है में मौजूद है मैं निम्नलिखित कोड पेशकश करने के लिए किया है:

public void insertOrReplace(String key, String value) { 
     boolean updated = false; 
     do { 
      String oldValue = concurrentMap.get(key); 
      if (oldValue == null) { 
       oldValue = concurrentMap.putIfAbsent(key, value); 
       if (oldValue == null) { 
        updated = true; 
       } 
      } 
      if (oldValue != null) { 
       final String newValue = recalculateNewValue(oldValue, value); 
       updated = concurrentMap.replace(key, oldValue, newValue); 
      } 
     } while (!updated); 
    } 

क्या आपको लगता है कि यह सही और धागा-सुरक्षित है?

क्या कोई आसान तरीका है?

उत्तर

6

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

public void insertOrReplace(String key, String value) { 
    for (;;) { 
     String oldValue = concurrentMap.putIfAbsent(key, value); 
     if (oldValue == null) 
      return; 

     final String newValue = recalculateNewValue(oldValue, value); 
     if (concurrentMap.replace(key, oldValue, newValue)) 
      return; 
    } 
} 
+0

मैं देखता हूं, धन्यवाद। यह थोड़ा और कॉम्पैक्ट है। – Holger

+0

'स्ट्रिंग' को छोड़ने और सब कुछ सीधे 'if' में डालने के बारे में क्या? – elect

+1

यदि कोई अन्य थ्रेड PutIfAbsent के बीच मान बदलता है और आपको एक असंगत मान के साथ समाप्त करता है। – Uberto

2

आपकी विधि थ्रेड सुरक्षित लगता है। यदि आपको ConcurrentHashMap के प्रदर्शन लाभों की आवश्यकता नहीं है, तो इसके बजाय नियमित हैश मैप का उपयोग करने पर विचार करें और इसकी सभी पहुंच सिंक्रनाइज़ करें। आपकी विधि AtomicInteger.getAndSet (int) के समान है, इसलिए यह ठीक होना चाहिए। मुझे संदेह है कि ऐसा करने का एक आसान तरीका है जब तक कि आप अपने लिए काम करने के लिए लाइब्रेरी कॉल की तलाश नहीं कर रहे हैं।

+0

विचार सिंक्रनाइज़ करना/ताला से बचने के लिए किया गया था के लिए एक committer हूँ क्योंकि उस तक पहुँच प्रदर्शन के लिए महत्वपूर्ण हो जाएगा। – Holger

2

मुझे नहीं लगता कि यह सही है। जैसा कि मैं समझता हूं कि विलय() विधि नौकरी के लिए सही उपकरण होगी। मुझे वर्तमान में एक ही समस्या है और परिणामों को देखने के लिए एक लिट परीक्षा लिखी है।

यह परीक्षण 100 श्रमिकों को शुरू करता है। उनमें से प्रत्येक 100 बार मानचित्र में मूल्य बढ़ा रहा है। तो अपेक्षित परिणाम 10000 होगा।

दो प्रकार के श्रमिक हैं। एक जो प्रतिस्थापन एल्गोरिदम का उपयोग करता है और उस पर विलय का उपयोग करता है। परीक्षण विभिन्न कार्यान्वयन के साथ दो बार चलाया जाता है।

import java.util.concurrent.ArrayBlockingQueue;                  
import java.util.concurrent.ConcurrentHashMap;                  
import java.util.concurrent.ConcurrentMap;                   
import java.util.concurrent.ExecutorService;                   
import java.util.concurrent.ThreadPoolExecutor;                  
import java.util.concurrent.TimeUnit;                    

public class ConcurrentMapTest                      
{                             

    private static ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();         

    private final class ReplaceWorker implements Runnable                
    {                             
     public void run()                        
     {                            
     for(int i = 0; i<100; i++)                     
     {                           
      Integer putIfAbsent = map.putIfAbsent("key", Integer.valueOf(1));          
      if(putIfAbsent == null)                     
       return;                        
      map.replace("key", putIfAbsent + 1);                  
     }                           
     }                            
    }                             

    private final class MergeWorker implements Runnable                
    {                             
     public void run()                        
     {                            
     for(int i = 0; i<100; i++)                     
     {                           
      map.merge("key", Integer.valueOf(1), (ov, nv) -> {              
       return ov + 1;                      
      });                          
     }                           
     }                            
    }                             

    public MergeWorker newMergeWorker()                    
    {                             
     return new MergeWorker();                      
    }                             

    public ReplaceWorker newReplaceWorker()                   
    {                             
     return new ReplaceWorker();                     
    }                             

    public static void main(String[] args)                   
    {                             
     map.put("key", 1);                        
     ConcurrentMapTest test = new ConcurrentMapTest();                
     ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQu 
     for(int i = 0; i<100; i++)                      
     {                            
     threadPool.submit(test.newMergeWorker());                 
     }                            
     awaitTermination(threadPool);                     
     System.out.println(test.map.get("key"));                  

     map.put("key", 1);                        
     threadPool = new ThreadPoolExecutor(10, 10, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1000));  
     for(int i = 0; i<100; i++)                      
     {                            
     threadPool.submit(test.newReplaceWorker());                 
     }                            
     awaitTermination(threadPool);                     
     System.out.println(test.map.get("key"));                  
    }                             

    private static void awaitTermination(ExecutorService threadPool)             
    {                             
     try                           
     {                            
     threadPool.shutdown();                      
     boolean awaitTermination = threadPool.awaitTermination(1, TimeUnit.SECONDS);        
     System.out.println("terminted successfull: " + awaitTermination);           
     }                            
     catch (InterruptedException e)                     
     {                            
     // TODO Auto-generated catch block                   
     e.printStackTrace();                      
     }                            
    }                             
}                       
 
result: 
terminted successfull: true 
10000 
terminted successfull: true 
1743 

समस्या बने रहें और अपने मामले में डाल के बीच एक अंतर है कि वहाँ है, तो मानचित्र परिणामों को समवर्ती accsess साथ अधिलेखित हो रहा है। विलय के साथ यह एक परमाणु संचालन है हालांकि दस्तावेज इसके बारे में कुछ भी नहीं कहता है।

+2

शायद यह ध्यान देने योग्य है कि विलय के लिए जावा 8 की आवश्यकता है। – assylias

+0

यह एकमात्र सही समाधान है। जावा 8 से पहले आपको अपने स्वयं के ताले लगाएंगे – Uberto

1

आप Eclipse Collections से MutableMapIterable.updateValueWith(K key, Function0<? extends V> factory, Function2<? super V,? super P,? extends V> function, P parameter) का उपयोग कर सकते हैं।

factory तर्क किसी प्रारंभिक मान को बनाता है यदि कोई भी मानचित्र में नहीं है। function तर्क को नए मान मूल्य के साथ आने के लिए अतिरिक्त पैरामीटर के साथ मानचित्र मूल्य पर लागू किया गया है। parameter को updateValueWith() पर अंतिम तर्क के रूप में पारित किया गया है। फ़ंक्शन को उस मामले में भी बुलाया जाता है जहां कुंजी मानचित्र में नहीं थी। तो प्रारंभिक मान वास्तव में functionfactory और parameter के आउटपुट पर लागू होता है। function को मान को म्यूटेट नहीं करना चाहिए; इसे एक नया मूल्य वापस करना चाहिए। आपके उदाहरण में, नक्शा मान स्ट्रिंग्स हैं जो अपरिवर्तनीय हैं इसलिए हम ठीक हैं।

org.eclipse.collections.impl.map.mutable.ConcurrentHashMap जैसे समवर्ती मैप्स में updateValueWith() का कार्यान्वयन थ्रेड-सुरक्षित और परमाणु भी है। यह महत्वपूर्ण है कि function मानचित्र मानों को म्यूटेट नहीं करता है या यह थ्रेड-सुरक्षित नहीं होगा। इसे इसके बजाय नए मान वापस करना चाहिए। आपके उदाहरण में, नक्शा मान स्ट्रिंग्स हैं जो अपरिवर्तनीय हैं इसलिए हम ठीक हैं।

यदि आपकी विधि recalculateNewValue() केवल स्ट्रिंग concatenation करता है, तो आप updateValueWith() का उपयोग कैसे कर सकते हैं।

Function0<String> factory =() -> "initial "; 
Function2<String, String, String> recalculateNewValue = String::concat; 

MutableMap<String, String> map = new ConcurrentHashMap<>(); 
map.updateValueWith("test", factory, recalculateNewValue, "append1 "); 
Assert.assertEquals("initial append1 ", map.get("test")); 
map.updateValueWith("test", factory, recalculateNewValue, "append2"); 
Assert.assertEquals("initial append1 append2", map.get("test")); 

आप एक ही बात को पूरा करने के जावा 8 के ConcurrentMap.compute(K key, BiFunction remappingFunction) उपयोग कर सकते हैं, लेकिन यह कुछ नुकसान है।

ConcurrentMap<String, String> map = new ConcurrentHashMap<>(); 
map.compute("test", (key, oldValue) -> oldValue == null ? "initial append1 " : oldValue + "append1 "); 
Assert.assertEquals("initial append1 ", map.get("test")); 
map.compute("test", (key, oldValue) -> oldValue == null ? "initial append1 " : oldValue + "append2"); 
Assert.assertEquals("initial append1 append2", map.get("test")); 
  • अलग से कोई कारखाना इतना लैम्ब्डा के शरीर मूल्यों और प्रारंभिक मान से निपटने के लिए है अनुपस्थित कुंजी के मामले को संभालने के लिए नहीं है।
  • एपीआई लैम्ब्स का पुन: उपयोग करने के लिए उपयुक्त नहीं है। updateValueWith() पर प्रत्येक कॉल एक ही लैम्ब्डा साझा करता है, लेकिन compute() पर प्रत्येक कॉल ढेर पर नया कचरा बनाता है।

नोट: मैं, ग्रहण संग्रह