जावा में अधिक कुशल थ्रेडसेफ कतार कौन सा है

एक साधारण कार्य है: कई धागे MyClass.add() फ़ंक्शन को कॉल करते हैं, और एक थ्रेड उनकी सेवा करने का प्रयास करता है।
मेरा प्रश्न: बेहतर या अधिक कुशल समाधान कौन सा है?

1st approach: with CopyOnWriteArrayList

@Singleton
public class myClass {

    List list = new CopyOnWriteArrayList();
    boolean isRunning = false;

    //this is called from many threads
    public void add(myType x){
        list.add(x);
    }


    //this is called from 1 thread
    public void start(){
        if (isRunning) return;
        isRunning = true;

        while (!list.isEmpty()) {
            myType curr = list.remove(0);

            //do something with curr...
        }
        isRunning = false;
    }
}



2nd approach with simple locks:

@Singleton
public class myClass {

    List list = new ArrayList();
    boolean isRunning = false;
    private final Lock _mutex = new ReentrantLock(true);

    //this is called from many threads
    public void add(myType x){
        _mutex.lock();
        list.add(x);
        _mutex.unlock();
    }


    //this is called from 1 thread
    public void start(){
        if (isRunning) return;
        isRunning = true;

        while (!list.isEmpty()) {
            _mutex.lock();
            myType curr = list.remove(0);
            _mutex.unlock();

            //do something with curr...
        }
        isRunning = false;
    }
}



3rd approach: with ConcurrentLinkedQueue

@Singleton
public class myClass {

    ConcurrentLinkedQueue list = new ConcurrentLinkedQueue();
    boolean isRunning = false;

    //this is called from many threads
    public void add(myType x){
        list.add(x);
    }


    //this is called from 1 thread
    public void start(){
        if (isRunning) return;
        isRunning = true;

        while (!list.isEmpty()) {
            //list cannot be empty at this point: other threads can't remove any items 
            myType curr = list.poll();

            //do something with curr...
        }
        isRunning = false;
    }
}



And this was the original wrong solution. I don't know why it gave sometimes (>100 threads) ConcurrentModificationException (despite of iterator and "synchronized"):

@Singleton
public class myClass {

    List list = Collections.synchronizedList(new ArrayList());
    boolean isRunning = false;

    //this is called from many threads
    public void add(myType x){
        synchronized(list) {
            list.add(x);
        }
    }


    //this is called from 1 thread
    public void start(){
        if (isRunning) return;
        isRunning = true;

        for (ListIterator iter = list.listIterator(); iter.hasNext();){
            myType curr = iter.next();

            //do something with curr...

            synchronized(list) {
                iter.remove(); //sometime it gives ConcurrentModificationException!
            }
        }
        isRunning = false;
    }
}
2
एक त्वरित प्रदर्शन रैपर की तरह लगता है कि वाई कतार लेनदेन करने वाले एक्स धागे को फोर्क करता है। इसका लाभ उठाएं!
जोड़ा लेखक Gray, स्रोत
यह इस बात पर निर्भर करता है कि आपके उद्देश्य क्या हैं।
जोड़ा लेखक Adam Arold, स्रोत
@ एडम अरोल्ड: थ्रेडसेफ, मेमोरी-फ्रेंडली और तेज़ होना। :)
जोड़ा लेखक steve, स्रोत
@AlexeiKaigorodov: सबसे खराब मामला 100 आइटम प्रति सेकंड कतार में आता है, और उनमें से एक को सेवा देने में 4 एस लगते हैं। लेकिन ज्यादातर समय कतार में कोई गतिविधि नहीं होती है। धन्यवाद!
जोड़ा लेखक steve, स्रोत
@AlexeiKaigorodov: धन्यवाद, मैंने इसे इस तरह से करने का फैसला किया।
जोड़ा लेखक steve, स्रोत
आपका मूल गलत समाधान <थ्रेड> सिंक्रनाइज़ ब्लॉक से पहले लूप के अंदर सूची को संशोधित करने से अन्य धागे को रोकता नहीं है।
जोड़ा लेखक SLaks, स्रोत
यह कई कारणों में से एक है कि संग्रह। सिंक्रनाइज़ *() बेकार हैं।
जोड़ा लेखक SLaks, स्रोत
कतार के माध्यम से प्रति सेकंड कितने आइटम करते हैं, और सर्वर थ्रेड किसी आइटम की सेवा करने में कितना समय व्यतीत करता है?
जोड़ा लेखक Alexei Kaigorodov, स्रोत
@steve इस संख्या के साथ, कतार में वस्तुओं को रखने/प्राप्त करने के लिए समय व्यतीत करने के लिए समय की तुलना में महत्वहीन है। सबसे कॉम्पैक्ट, भरोसेमंद और पठनीय समाधान (कहते हैं, ब्लॉकिंग लिंक्ड क्यूयू) चुनें और दक्षता के बारे में भूल जाओ। इसके बारे में सोचना शुरू करें जब दर प्रति सेकंड 1 मिलियन तक पहुंच जाती है।
जोड़ा लेखक Alexei Kaigorodov, स्रोत

1 उत्तर

सामान्य नियम यह है: वह जो आपकी समस्या को सर्वोत्तम बनाता है।

लॉक वेरिएंट सब कुछ धीमा कर रहा है, क्योंकि अगर वे लॉक भाग में प्रवेश करते हैं तो सभी धागे पकड़ते हैं, भले ही इसकी आवश्यकता नहीं है (यदि 5 तत्व हैं, तो 5 थ्रेड उन्हें एक साथ मतदान कर सकते हैं, केवल 6 वें हैं प्रतीक्षा करने के लिए)। हालांकि यह समाधान अच्छा है, यदि आपके पास एकवचन संसाधन है जिसे नेटवर्क कनेक्शन या फ़ाइल की तरह साझा नहीं किया जा सकता है।

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

ConcurrentLinkedQueue सबसे अच्छा समाधान है, यदि पढ़ने और लिखने की मात्रा बराबर होती है, इसलिए नाम कतार है। तो यह आपके मामले को सबसे अच्छा फिट करना चाहिए।

इसके अतिरिक्त आपको अपने कोड में गंभीर त्रुटि है:

while (!list.isEmpty()) {
  myType curr = list.poll();

सूची सिर्फ गारंटी देती है कि प्रत्येक कॉल परमाणु रूप से किया जाता है, लेकिन आपका कोड स्वचालित रूप से थ्रेड-सुरक्षित नहीं होता है क्योंकि आप इसका उपयोग करते हैं। इस उदाहरण में सूची isEmpty() और poll() के बीच पहले ही संशोधित हो सकती है, इसलिए यह isEmpty() कॉल पर 1 तत्व हो सकता है, लेकिन फिर एक बार मतदान करने के बाद कोई नहीं। यह नल लौटकर ConcurrentLinkedQueue द्वारा कृपा से संभाला जाता है, लेकिन आपके कोड से नहीं। तो सही रूप होगा:

myType curr;
while ((curr = list.poll()) != null) {

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

आपके निकालें (0) कॉल के लिए भी यह सच है, यदि यह अंतिम तत्व को किसी अन्य थ्रेड द्वारा हटा दिया गया है, तो यह IndexOutOfBoundsException फेंक सकता है।

1
जोड़ा
मेरे कोड की समीक्षा के लिए धन्यवाद। मैंने ConcurrentLinkedQueue चुना है। :) त्रुटि के लिए: इस मामले में केवल एक धागा फ़ंक्शन प्रारंभ() (बूलियन isRunning इसे सुरक्षित करता है) दर्ज कर सकता है, इसलिए यह असंभव है कि सूची का आकार IsEmpty() और मतदान() से पहले छोटा है। (जैसा कि मैंने कोड में टिप्पणी की थी)। लेकिन सामान्य रूप से आपके पास सही है।
जोड़ा लेखक steve, स्रोत
ओह, मैं यह कहना भूल गया कि यह वर्ग एक सिंगलटन बीन है। तो आश्चर्यजनक सुरक्षा है।
जोड़ा लेखक steve, स्रोत
आपका isRunning उसी कारण से कुछ भी नहीं बचाता है। ;) और यदि आप इसे अस्थिर के रूप में परिभाषित नहीं करते हैं, तो प्रत्येक धागा किसी भी तरह से अपनी स्थानीय प्रतिलिपि के आधार पर काम करेगा। एक परमाणु बूलियन आपकी मदद कर सकता है।
जोड़ा लेखक TwoThe, स्रोत