2011-11-30 12 views
6

मेरे पास कई सिंगलटन कार्यान्वयन के लिए एक आम इंटरफ़ेस है। इंटरफेस प्रारंभिक विधि को परिभाषित करता है जो चेक अपवाद फेंक सकता है।सिंगलटन ऑब्जेक्ट्स की फैक्टरी: क्या यह कोड थ्रेड-सुरक्षित है?

मुझे एक कारखाने की आवश्यकता है जो मांग पर कैश किए गए सिंगलटन कार्यान्वयन को वापस कर देगा, और आश्चर्य होगा कि निम्नलिखित दृष्टिकोण थ्रेड-सुरक्षित है या नहीं?

Update1: करें, कोई भी 3 आंशिक रूप से पुस्तकालयों का सुझाव नहीं है के रूप में यह संभव लाइसेंस के मुद्दों की वजह से कानूनी मंजूरी प्राप्त करने के लिए :-)

UPDATE2 की आवश्यकता होगी: इस कोड को होने की संभावना में इस्तेमाल किया जाएगा होगा ईजेबी पर्यावरण, इसलिए यह अतिरिक्त धागे को बढ़ाने या उस तरह की सामग्री का उपयोग करने के लिए पसंद नहीं है।

interface Singleton 
{ 
    void init() throws SingletonException; 
} 

public class SingletonFactory 
{ 
    private static ConcurrentMap<String, AtomicReference<? extends Singleton>> CACHE = 
     new ConcurrentHashMap<String, AtomicReference<? extends Singleton>>(); 

    public static <T extends Singleton> T getSingletonInstance(Class<T> clazz) 
     throws SingletonException 
    { 
     String key = clazz.getName(); 
     if (CACHE.containsKey(key)) 
     { 
      return readEventually(key); 
     } 

     AtomicReference<T> ref = new AtomicReference<T>(null); 
     if (CACHE.putIfAbsent(key, ref) == null) 
     { 
      try 
      { 
       T instance = clazz.newInstance(); 
       instance.init(); 
       ref.set(instance); // ----- (1) ----- 
       return instance; 
      } 
      catch (Exception e) 
      { 
       throw new SingletonException(e); 
      } 
     } 

     return readEventually(key); 
    } 

    @SuppressWarnings("unchecked") 
    private static <T extends Singleton> T readEventually(String key) 
    { 
     T instance = null; 
     AtomicReference<T> ref = (AtomicReference<T>) CACHE.get(key); 
     do 
     { 
      instance = ref.get(); // ----- (2) ----- 
     } 
     while (instance == null); 
     return instance; 
    } 
} 

मुझे लाइनों (1) और (2) के बारे में पूरी तरह से यकीन नहीं है। मुझे पता है कि संदर्भित ऑब्जेक्ट AtomicReference में अस्थिर क्षेत्र के रूप में घोषित किया गया है, और इसलिए रेखा (1) में किए गए परिवर्तन तुरंत लाइन (2) पर दिखाई दे सकते हैं - लेकिन अभी भी कुछ संदेह हैं ...

इसके अलावा - मुझे लगता है ConcurrentHashMap का उपयोग कैश में नई कुंजी डालने की परमाणुता को संबोधित करता है।

क्या आप इस दृष्टिकोण के साथ कोई चिंता देखते हैं? धन्यवाद!

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

उत्तर

0

गुवा के CacheBuilder का उपयोग करने पर विचार करें। उदाहरण के लिए:

private static Cache<Class<? extends Singleton>, Singleton> singletons = CacheBuilder.newBuilder() 
    .build(
     new CacheLoader<Class<? extends Singleton>, Singleton>() { 
     public Singleton load(Class<? extends Singleton> key) throws SingletonException { 
      try { 
      Singleton singleton = key.newInstance(); 
      singleton.init(); 
      return singleton; 
      } 
      catch (SingletonException se) { 
      throw se; 
      } 
      catch (Exception e) { 
      throw new SingletonException(e); 
      } 
     } 
     }); 

public static <T extends Singleton> T getSingletonInstance(Class<T> clazz) { 
    return (T)singletons.get(clazz); 
} 

नोट: यह उदाहरण अनचाहे और असम्बद्ध है।

अमरूद अंतर्निहित Cache कार्यान्वयन आपके लिए सभी कैशिंग और समवर्ती तर्क को संभालेगा।

+0

धन्यवाद! तृतीय पक्ष lib मेरे मामले में एक विकल्प नहीं है ... – anenvyguest

-1

कोड आमतौर पर थ्रेड सुरक्षित नहीं है क्योंकि CACHE.containsKey(key) चेक और CACHE.putIfAbsent(key, ref) कॉल के बीच एक अंतर है। विधि में (विशेष रूप से मल्टी-कोर/प्रोसेसर सिस्टम पर) एक साथ कॉल करने के लिए दो धागे के लिए संभव है और दोनों containsKey() जांच करें, फिर दोनों रख-रखाव और निर्माण कार्य करने का प्रयास करें।

मैं लॉक या किसी प्रकार की मॉनीटर पर सिंक्रनाइज़ करके getSingletonInstnace() विधि के निष्पादन की रक्षा करता हूं।

+1

वह 'अल्टिक रिफरेंस' को 'शून्य' मान के साथ डालकर इसके खिलाफ सुरक्षा करता है। यदि 'putIfAbsent() 'शून्य वापस नहीं आता है तो यह बस इसे छोड़ देता है और कॉल प्राप्त करता है। वह लूप का मुद्दा है। – Gray

3

इन समवर्ती/परमाणु चीजों के सभी के बाद सिर्फ गेटर आसपास

synchronized(clazz){} 

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

आप एक hashmap होने से इसे आगे का अनुकूलन कर सकता है, और केवल अगर वहाँ एक याद आती है, सिंक्रनाइज़ ब्लॉक का उपयोग करें:

public static <T> T get(Class<T> cls){ 
    // No lock try 
    T ref = cache.get(cls); 
    if(ref != null){ 
     return ref; 
    } 
    // Miss, so use create lock 
    synchronized(cls){ // singletons are double created 
     synchronized(cache){ // Prevent table rebuild/transfer contentions -- RARE 
      // Double check create if lock backed up 
      ref = cache.get(cls); 
      if(ref == null){ 
       ref = cls.newInstance(); 
       cache.put(cls,ref); 
      } 
      return ref; 
     } 
    } 
} 
+0

धन्यवाद! मैं इस दृष्टिकोण पर विचार कर रहा था, लेकिन "जावा कंसुरेंसी इन प्रैक्टिस" पुस्तक में एक तर्क था, कि मध्यम भार के तहत परमाणु-आधारित एल्गोरिदम लॉक कक्षाओं के आधार पर उनको बेहतर प्रदर्शन करते हैं जो बदले में आंतरिक लॉक-आधारित वाले प्रदर्शन करते हैं। हालांकि, मैं आपके द्वारा सुझाए गए कोड की ओर झुकता हूं क्योंकि यह बहुत अधिक पठनीय है :-) – anenvyguest

+0

हां, लेकिन एक सिंगलटन के रूप में, आप कभी भी लोड नहीं करेंगे, क्योंकि सृजन ही एकमात्र लॉक हिस्सा है। गेटर्स सभी अन-सिंक्रनाइज़ हैं। और मेरे अनुभव में, कताई हमेशा लॉक-अवरुद्ध से भी बदतर रही है। – Nthalk

+0

मेरी पिछली टिप्पणी के अलावा - मुझे लगता है कि सादा java.util.HashMap यहां पर्याप्त नहीं होगा क्योंकि 'cache.put (cls, ref)' आंतरिक तालिका के पुनर्निर्माण को ट्रिगर कर सकता है और इसलिए 'cache.get (cls) 'can नक्शा को असंगत स्थिति में देखें, क्योंकि बाद की कॉल 'सिंक्रनाइज़' ब्लॉक के बाहर निकलती है। – anenvyguest

0

यह लगता है कि यह हालांकि मैं अगर यहां तक ​​कि नींद में किसी प्रकार का विचार कर सकते हैं काम करेगा संदर्भ के लिए परीक्षण करते समय एक नैनोसेकंद या कुछ। स्पिन टेस्ट लूप बेहद महंगा होगा।

इसके अलावा, मैं AtomicReferencereadEventually() को गुजर ताकि आप containsKey() और फिर putIfAbsent() रेस स्थिति से बच सकते हैं द्वारा कोड में सुधार पर विचार करेंगे। तो कोड होगा:

AtomicReference<T> ref = (AtomicReference<T>) CACHE.get(key); 
if (ref != null) { 
    return readEventually(ref); 
} 

AtomicReference<T> newRef = new AtomicReference<T>(null); 
AtomicReference<T> oldRef = CACHE.putIfAbsent(key, newRef); 
if (oldRef != null) { 
    return readEventually(oldRef); 
} 
... 
3

आप तुल्यकालन से बचने के लिए बहुत काम करने के लिए चले गए हैं, और मुझे लगता है ऐसा करने के लिए कारण प्रदर्शन चिंताओं के लिए है। क्या आपने यह देखने के लिए परीक्षण किया है कि क्या यह वास्तव में एक सिंक्रनाइज़ समाधान बनाम प्रदर्शन में सुधार करता है?

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

इसके अतिरिक्त, मुझे लगता है कि जब आप सिंगलटन एक्सेप्शन को example.init() पर कॉल के दौरान फेंक दिया जाता है तो आप संभवतः एक अनंत लूप के साथ समाप्त हो सकते हैं। कारण यह है कि एक समवर्ती धागा पढ़ने में इंतजार कर रहा है, कभी भी इसके उदाहरण को कभी खत्म नहीं करेगा (क्योंकि एक अपवाद फेंक दिया गया था जबकि एक और धागा उदाहरण शुरू कर रहा था)। हो सकता है कि यह आपके मामले के लिए सही व्यवहार है, या हो सकता है कि आप प्रतीक्षा थ्रेड पर फेंकने के लिए अपवाद को ट्रिगर करने के लिए उदाहरण के लिए कुछ विशेष मान सेट करना चाहते हैं।

+0

अच्छा पकड़ो!जब अपवाद फेंक दिया जाता है तो मैं असीमित लूप को याद करता हूं। – anenvyguest

-1

google "Memoizer"। मूल रूप से, AtomicReference के बजाय, Future का उपयोग करें।