धागे को बढ़ाने के लिए थ्रेडपूल एक्सक्यूटर को कतार में लगाने से पहले अधिकतम कैसे प्राप्त करें?


99

मैं कुछ समय के लिए डिफ़ॉल्ट व्यवहार के साथ निराश हो गया हूं ThreadPoolExecutorजो ExecutorServiceथ्रेड-पूल का समर्थन करता है जो कि हम में से बहुत से उपयोग करते हैं। Javadocs से उद्धृत करने के लिए:

अगर कोरपूलसाइज से ज्यादा हैं, लेकिन चलने वाले मैक्सिमम थैली से कम हैं, तो एक नया धागा तभी बनाया जाएगा जब कतार पूरी भर जाएगी

इसका मतलब यह है कि यदि आप निम्न कोड के साथ एक थ्रेड पूल को परिभाषित करते हैं, तो यह कभी भी 2 थ्रेड शुरू नहीं करेगा क्योंकि LinkedBlockingQueueयह अनबाउंड है।

ExecutorService threadPool =
   new ThreadPoolExecutor(1 /*core*/, 50 /*max*/, 60 /*timeout*/,
      TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(/* unlimited queue */));

यदि आपके पास एक बंधी हुई कतार है और कतार पूरी भरी हुई है तो शुरू की गई कोर संख्या के ऊपर कोई धागा है। मुझे संदेह है कि बड़ी संख्या में जूनियर जावा मल्टीथ्रेडेड प्रोग्रामर इस व्यवहार से अनजान हैं ThreadPoolExecutor

अब मेरे पास विशिष्ट उपयोग का मामला है जहां यह इष्टतम नहीं है। मैं अपने खुद के टीपीई वर्ग को लिखने के बिना, इसके चारों ओर काम करने के तरीकों की तलाश कर रहा हूं।

मेरी आवश्यकताएं एक वेब सेवा के लिए हैं जो संभवतः 3 जी पार्टी के लिए कॉल बैक कर रही हैं।

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

मैं इस सीमा के आसपास कैसे काम कर सकता हूं, ThreadPoolExecutorजहां अधिक धागे शुरू करने से पहले कतार को बाध्य करने और पूर्ण करने की आवश्यकता है ? कार्यों को कतारबद्ध करने से पहले अधिक धागे शुरू करने के लिए मैं इसे कैसे प्राप्त कर सकता हूं ?

संपादित करें:

@Flavio ThreadPoolExecutor.allowCoreThreadTimeOut(true)कोर थ्रेड्स टाइमआउट और निकास का उपयोग करने के बारे में एक अच्छा बिंदु बनाता है । मुझे लगता है कि लेकिन मुझे अभी भी कोर-थ्रेड्स की सुविधा चाहिए थी। मैं नहीं चाहता था कि यदि संभव हो तो कोर-आकार के नीचे पूल में थ्रेड्स की संख्या घट जाए।


1
यह देखते हुए कि आपका उदाहरण अधिकतम 10 धागे बनाता है, क्या एक निश्चित आकार के थ्रेड पूल पर बढ़ने / सिकुड़ने वाली चीज़ों का उपयोग करने में कोई वास्तविक बचत है?
21

अच्छी बात @bstempi। संख्या कुछ हद तक मनमानी थी। मैंने इसे 50 के प्रश्न में बढ़ा दिया है। मुझे यकीन नहीं है कि मैं वास्तव में अभी तक कितने समवर्ती धागे काम करना चाहता हूं, क्योंकि मेरे पास इसका समाधान है।
ग्रे

1
ओह नहीं! अगर मैं यहाँ हो सकता है 10 upvotes, बिल्कुल मैं उसी स्थिति में हूँ।
यूजीन

जवाबों:


50

मैं इस सीमा के आसपास कैसे काम कर सकता हूं, ThreadPoolExecutorजहां अधिक धागे शुरू करने से पहले कतार को बाध्य करना होगा और पूर्ण करना होगा।

मेरा मानना ​​है कि मैंने आखिरकार इस सीमा तक कुछ हद तक सुरुचिपूर्ण (शायद थोड़ा हैसी) समाधान पाया है ThreadPoolExecutor। इसमें यह शामिल LinkedBlockingQueueहै कि जब कुछ कार्य पहले से ही कतारबद्ध हों false, queue.offer(...)तब इसे वापस करना होगा। यदि मौजूदा थ्रेड्स पंक्तिबद्ध कार्यों के साथ नहीं चल रहे हैं, तो TPE अतिरिक्त थ्रेड्स जोड़ देगा। यदि पूल पहले से ही अधिकतम धागे पर है, तो RejectedExecutionHandlerवसीयत को बुलाया जाएगा। यह हैंडलर है जो तब put(...)कतार में करता है ।

यह निश्चित रूप से एक कतार लिखना अजीब है जहां offer(...)वापस आ सकते हैं falseऔर put()कभी भी ब्लॉक नहीं करते हैं ताकि हैक हिस्सा हो। लेकिन यह TPE कतार के उपयोग के साथ अच्छी तरह से काम करता है इसलिए मुझे ऐसा करने में कोई समस्या नहीं दिखती है।

यहाँ कोड है:

// extend LinkedBlockingQueue to force offer() to return false conditionally
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
    private static final long serialVersionUID = -6903933921423432194L;
    @Override
    public boolean offer(Runnable e) {
        // Offer it to the queue if there is 0 items already queued, else
        // return false so the TPE will add another thread. If we return false
        // and max threads have been reached then the RejectedExecutionHandler
        // will be called which will do the put into the queue.
        if (size() == 0) {
            return super.offer(e);
        } else {
            return false;
        }
    }
};
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1 /*core*/, 50 /*max*/,
        60 /*secs*/, TimeUnit.SECONDS, queue);
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        try {
            // This does the actual put into the queue. Once the max threads
            //  have been reached, the tasks will then queue up.
            executor.getQueue().put(r);
            // we do this after the put() to stop race conditions
            if (executor.isShutdown()) {
                throw new RejectedExecutionException(
                    "Task " + r + " rejected from " + e);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return;
        }
    }
});

इस तंत्र के साथ, जब मैं कतार में कार्य प्रस्तुत करता हूं, तो ThreadPoolExecutor:

  1. प्रारंभ में कोर आकार तक थ्रेड्स की संख्या को स्केल करें (यहां 1)।
  2. इसे कतार में चढ़ाएं। यदि कतार खाली है, तो इसे मौजूदा थ्रेड्स द्वारा संभाला जाएगा।
  3. यदि कतार में 1 या अधिक तत्व पहले से ही हैं, तो offer(...)वह झूठी हो जाएगी।
  4. यदि झूठी लौटा दी जाती है, तो पूल में थ्रेड्स की संख्या को तब तक स्केल करें जब तक वे अधिकतम संख्या (50) तक नहीं पहुंच जाते।
  5. यदि अधिकतम पर तो यह कॉल करता है RejectedExecutionHandler
  6. RejectedExecutionHandlerकतार में तो डालता कार्य फीफो क्रम में पहली उपलब्ध धागा द्वारा संसाधित किया जा करने के लिए।

यद्यपि मेरे ऊपर दिए गए उदाहरण कोड में, कतार अनबाउंड है, आप इसे एक बंधी हुई कतार के रूप में भी परिभाषित कर सकते हैं। उदाहरण के लिए, यदि आप 1000 की क्षमता जोड़ते हैं LinkedBlockingQueueतो यह होगा:

  1. अधिकतम तक थ्रेड्स को स्केल करें
  2. तब तक कतारबद्ध रहें जब तक कि यह 1000 कार्यों से भरा न हो
  3. तब तक कॉलर को ब्लॉक करें जब तक कि कतार के लिए जगह उपलब्ध न हो जाए।

इसके अलावा, यदि आपको तत्कालीन समय offer(...)में उपयोग करने की आवश्यकता है, तो आप टाइमआउट के बजाय विधि का RejectedExecutionHandlerउपयोग कर सकते हैं ।offer(E, long, TimeUnit)Long.MAX_VALUE

चेतावनी:

यदि आपको शटडाउन के बाद कार्यों को निष्पादक में जोड़ने की उम्मीद है , तो हो सकता है कि जब आप निष्पादक-सेवा बंद कर दी गई हो, तो आप RejectedExecutionExceptionहमारे रिवाज को फेंकने के बारे में और अधिक सचेत होना चाहते हैं RejectedExecutionHandler। इसे इंगित करने के लिए @RaduToader को धन्यवाद।

संपादित करें:

इस उत्तर के लिए एक और ट्वीक टीपीई से पूछा जा सकता है कि क्या कोई निष्क्रिय थ्रेड्स हैं और यदि ऐसा है तो केवल आइटम को एनक्यू करें। आपको इसके लिए एक सच्चा वर्ग बनाना होगा और ourQueue.setThreadPoolExecutor(tpe);उस पर विधि जोड़ना होगा।

तब आपकी offer(...)विधि कुछ इस तरह दिख सकती है:

  1. यह देखने के लिए जांचें कि क्या tpe.getPoolSize() == tpe.getMaximumPoolSize()किस मामले में सिर्फ कॉल करना है super.offer(...)
  2. और अगर tpe.getPoolSize() > tpe.getActiveCount()तब से कॉल करें, तो super.offer(...)लगता है कि बेकार धागे हैं।
  3. अन्यथा falseएक और धागा कांटा लौटाएं ।

शायद यह:

int poolSize = tpe.getPoolSize();
int maximumPoolSize = tpe.getMaximumPoolSize();
if (poolSize >= maximumPoolSize || poolSize > tpe.getActiveCount()) {
    return super.offer(e);
} else {
    return false;
}

ध्यान दें कि TPE पर प्राप्त तरीके महंगे हैं क्योंकि वे volatileखेतों तक पहुँचते हैं या (मामले में getActiveCount()) TPE को लॉक करते हैं और थ्रेड-लिस्ट चलते हैं। इसके अलावा, यहां दौड़ की स्थितियां भी हैं जिनके कारण कोई कार्य अनुचित तरीके से किया जा सकता है या जब कोई थ्रेड थ्रेड होता है तो एक अन्य थ्रेड कांटा जाता है।


मैं भी एक ही समस्या से जूझ रहा था, निष्पादन पद्धति को ओवरराइड करने में समाप्त हो गया। लेकिन यह वास्तव में एक अच्छा समाधान है। :)
बत्तीस

मुझे Queueयह पूरा करने के लिए अनुबंध को तोड़ने का विचार पसंद नहीं है , आप निश्चित रूप से अपने विचार में अकेले नहीं हैं: groovy-programming.com/post/26923146865
bstempi

3
क्या आपको यहां एक विषमता नहीं मिलती है कि पहले कुछ कार्यों को कतारबद्ध किया जाएगा, और उसके बाद ही नए सूत्र सामने आएंगे? उदाहरण के लिए, यदि आपका एक कोर धागा एक लंबे समय से चल रहे कार्य के साथ व्यस्त है, और आप कॉल करते हैं execute(runnable), तो runnableबस कतार में जोड़ा जाता है। यदि आप कॉल करते हैं execute(secondRunnable), तो secondRunnableकतार में जोड़ा जाता है। लेकिन अब अगर आप कॉल करते हैं execute(thirdRunnable), तो thirdRunnableएक नए धागे में चलाया जाएगा। runnableऔर secondRunnableकेवल एक बार चलाने के thirdRunnable(या मूल लंबे समय से चल काम) पूरा कर लें।
रॉबर्ट टुपेलो-श्नाइक

1
हां, रॉबर्ट सही है, एक बहु-बहुल वातावरण में, कतार कभी-कभी बढ़ती है जबकि उपयोग करने के लिए मुफ्त धागे होते हैं। नीचे का समाधान जो TPE का विस्तार करता है - बहुत बेहतर काम करता है। मुझे लगता है कि रॉबर्ट के सुझाव को जवाब के रूप में चिह्नित किया जाना चाहिए, भले ही उपरोक्त हैक दिलचस्प हो
वाना नो ऑल

1
शटडाउन पर निष्कासन में "RejectExecutionHandler" ने मदद की। अब आपको शटडाउन का उपयोग करने के लिए मजबूर किया जा रहा है। () शटडाउन के रूप में () नए कार्यों को जोड़ने से नहीं रोकता है (आवश्यक होने के कारण)
Radu Toader

28

मुख्य आकार और अधिकतम आकार को समान मान पर सेट करें, और कोर थ्रेड्स को पूल से निकालने की अनुमति दें allowCoreThreadTimeOut(true)


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

धन्यवाद! यह ऐसा करने का सबसे सरल तरीका है।
दिमित्री Ovchinnikov

28

मुझे इस सवाल पर पहले से ही दो अन्य उत्तर मिल गए हैं, लेकिन मुझे संदेह है कि यह सबसे अच्छा है।

यह वर्तमान में स्वीकृत उत्तर की तकनीक पर आधारित है :

  1. offer()(कभी-कभी) झूठी वापसी के लिए कतार की विधि को ओवरराइड करें ,
  2. जिसके कारण ThreadPoolExecutorया तो एक नया धागा पैदा होता है या वह कार्य अस्वीकार कर देता है, और
  3. वास्तव में अस्वीकृति पर कार्य को पंक्तिबद्ध करने के RejectedExecutionHandlerलिए सेट करें ।

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

समाधान जावा 7 का उपयोग करना LinkedTransferQueueऔर offer()कॉल करना है tryTransfer()। जब कोई प्रतीक्षा उपभोक्ता थ्रेड होता है, तो कार्य केवल उस थ्रेड के पास हो जाएगा। अन्यथा, offer()झूठे वापस आ जाएगा और ThreadPoolExecutorएक नया धागा पैदा करेगा।

    BlockingQueue<Runnable> queue = new LinkedTransferQueue<Runnable>() {
        @Override
        public boolean offer(Runnable e) {
            return tryTransfer(e);
        }
    };
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 50, 60, TimeUnit.SECONDS, queue);
    threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            try {
                executor.getQueue().put(r);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    });

मुझे सहमत होना होगा, यह मुझे सबसे साफ लगता है। समाधान के साथ केवल नकारात्मक पक्ष यह है कि LinkedTransferQueue अनबाउंड है, इसलिए आपको अतिरिक्त कार्य के बिना क्षमता-बाउंड कार्य कतार नहीं मिलती है।
येरोक

एक समस्या है जब पूल अधिकतम आकार तक बढ़ता है। यह कहें कि पूल अधिकतम आकार तक बढ़ा हुआ है और वर्तमान में प्रत्येक थ्रेड एक कार्य को अंजाम दे रहा है, जब रननेबल ने यह प्रस्ताव प्रस्तुत किया है तो निहितार्थ झूठा वापस आ जाएगा और थ्रेडपूल एक्ज़ीक्यूटर थ्रेडवर्क थ्रेड को जोड़ने का प्रयास करता है, लेकिन पूल पहले ही पहुंच गया है, इसलिए अधिकतम रनने योग्य बस अस्वीकार कर दिया जाएगा। आपके द्वारा लिखे गए अस्वीकृतExandHandler के अनुसार इसे फिर से कतार में पेश किया जाएगा जिसके परिणामस्वरूप यह बंदर नृत्य फिर से शुरू होगा।
सुधीरा

1
@ सुधीरा मेरा मानना ​​है कि आप गलत हैं। queue.offer(), क्योंकि यह वास्तव में बुला रहा है LinkedTransferQueue.tryTransfer(), झूठे लौटेगा और कार्य को नहीं करेगा। हालाँकि RejectedExecutionHandlerकॉल queue.put(), जो विफल नहीं होती है और कार्य को पूरा करती है।
रॉबर्ट टुपेलो-श्नेक

1
@ RobertTupelo-Schneck अत्यंत उपयोगी और अच्छा है!
यूजीन

1
@ RobertTupelo-Schneck एक आकर्षण की तरह काम करता है! मुझे नहीं पता कि जावा में बॉक्स से बाहर ऐसा कुछ क्यों नहीं है
जॉर्जी पीव

7

नोट: मैं अब अपने अन्य उत्तर को प्राथमिकता देता हूं और सुझाता हूं

यहां एक संस्करण है जो मुझे बहुत अधिक सीधा लगता है: जब भी कोई नया कार्य निष्पादित किया जाता है, तो corePoolSize (अधिकतम अधिकतम सीमा तक) बढ़ाएं, फिर corePoolSize (उपयोगकर्ता द्वारा निर्दिष्ट "कोर पूल आकार की सीमा के नीचे") को कम करें जब भी एक कार्य पूरा होता है।

इसे दूसरे तरीके से रखने के लिए, चल रहे या संलग्न कार्यों की संख्या पर नज़र रखें और सुनिश्चित करें कि CorePoolSize कार्यों की संख्या के बराबर है जब तक कि यह उपयोगकर्ता द्वारा निर्दिष्ट "कोर पूल आकार" और मैक्सपूलसाइज़ के बीच हो।

public class GrowBeforeQueueThreadPoolExecutor extends ThreadPoolExecutor {
    private int userSpecifiedCorePoolSize;
    private int taskCount;

    public GrowBeforeQueueThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        userSpecifiedCorePoolSize = corePoolSize;
    }

    @Override
    public void execute(Runnable runnable) {
        synchronized (this) {
            taskCount++;
            setCorePoolSizeToTaskCountWithinBounds();
        }
        super.execute(runnable);
    }

    @Override
    protected void afterExecute(Runnable runnable, Throwable throwable) {
        super.afterExecute(runnable, throwable);
        synchronized (this) {
            taskCount--;
            setCorePoolSizeToTaskCountWithinBounds();
        }
    }

    private void setCorePoolSizeToTaskCountWithinBounds() {
        int threads = taskCount;
        if (threads < userSpecifiedCorePoolSize) threads = userSpecifiedCorePoolSize;
        if (threads > getMaximumPoolSize()) threads = getMaximumPoolSize();
        setCorePoolSize(threads);
    }
}

लिखित रूप में वर्ग उपयोगकर्ता को बदलने का समर्थन नहीं करता निर्माण के बाद corePoolSize या maximumPoolSize निर्दिष्ट, और सीधे या के माध्यम से काम कतार से छेड़छाड़ का समर्थन नहीं करता remove()या purge()


मुझे यह पसंद है सिवाय synchronizedब्लॉकों के। क्या आप कार्यों की संख्या प्राप्त करने के लिए कतार से कॉल कर सकते हैं। या शायद एक का उपयोग करें AtomicInteger?
ग्रे

मैं उनसे बचना चाहता था, लेकिन समस्या यह है। यदि execute()अलग-अलग थ्रेड्स में कई कॉल हैं, तो प्रत्येक (1) यह पता लगाने के लिए कि कितने थ्रेड्स की आवश्यकता है, (2) setCorePoolSizeउस नंबर पर, और (3) कॉल करें super.execute()। यदि चरण (1) और (2) सिंक्रनाइज़ नहीं हैं, तो मुझे यकीन नहीं है कि एक दुर्भाग्यपूर्ण ऑर्डर को कैसे रोका जाए जहां आप उच्च संख्या के बाद कोर पूल का आकार कम संख्या में सेट करते हैं। सुपरक्लास क्षेत्र में सीधे पहुंच के साथ यह तुलना-और-सेट का उपयोग करके किया जा सकता है, लेकिन मुझे इसे बिना सिंक्रनाइज़ेशन के उपवर्ग में करने का एक साफ तरीका नहीं दिखता है।
रॉबर्ट टुपेलो-श्नेक

मुझे लगता है कि जब तक taskCountक्षेत्र वैध है (यानी a AtomicInteger) उस दौड़ की स्थिति के लिए दंड अपेक्षाकृत कम है । यदि दो धागे एक-दूसरे के तुरंत बाद पूल-आकार को पुन: व्यवस्थित करते हैं, तो इसे उचित मूल्य मिलना चाहिए। यदि दूसरा कोर धागे को सिकोड़ता है तो उसे कतार या कुछ में एक बूंद दिखाई देती है।
ग्रे

1
अफसोस की बात है कि मुझे लगता है कि यह उससे भी बदतर है। मान लीजिए 10 और 11 कार्य execute()। प्रत्येक कॉल करेगा atomicTaskCount.incrementAndGet()और उन्हें क्रमशः 10 और 11 मिलेंगे। लेकिन बिना सिंक्रोनाइज़ेशन (कार्य गणना के और कोर पूल का आकार निर्धारित किए हुए), तो आप प्राप्त कर सकते हैं (1) कार्य 11 कोर पूल आकार को 11 पर सेट करता है, (2) कार्य 10 कोर पूल आकार को 10, (3) पर सेट करता है। टास्क 10 कॉल्स super.execute(), (4) टास्क 11 कॉल्स super.execute()और एंकाउटेड है।
बजे रॉबर्ट तुपेलो-श्नेक

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

6

हमारे पास एक उपवर्ग है ThreadPoolExecutorजो एक अतिरिक्त creationThresholdऔर ओवरराइड लेता है execute

public void execute(Runnable command) {
    super.execute(command);
    final int poolSize = getPoolSize();
    if (poolSize < getMaximumPoolSize()) {
        if (getQueue().size() > creationThreshold) {
            synchronized (this) {
                setCorePoolSize(poolSize + 1);
                setCorePoolSize(poolSize);
            }
        }
    }
}

शायद यह भी मदद करता है, लेकिन तुम्हारा…


दिलचस्प। इसके लिए धन्यवाद। मुझे वास्तव में नहीं पता था कि मूल आकार परस्पर था।
ग्रे

अब जब मैं इसके बारे में कुछ और सोचता हूं, तो यह समाधान कतार के आकार की जांच करने के मामले में मेरा बेहतर है। मैंने अपने उत्तर को offer(...)केवल falseसशर्त रूप से वापस करने की विधि के लिए टाल दिया है । धन्यवाद!
ग्रे

4

अनुशंसित उत्तर JDK थ्रेड पूल के साथ समस्या का केवल एक (1) हल करता है:

  1. JDK थ्रेड पूल कतार के लिए पक्षपाती हैं। इसलिए एक नया धागा पैदा करने के बजाय, वे कार्य को पूरा करेंगे। यदि कतार अपनी सीमा तक पहुँचती है तो ही थ्रेड पूल एक नया धागा पैदा करेगा।

  2. लोड हल्का होने पर थ्रेड रिटायरमेंट नहीं होता है। उदाहरण के लिए, यदि हमारे पास पूल को मारते हुए नौकरियों का एक विस्फोट है जो पूल को अधिकतम करने का कारण बनता है, तो एक समय में अधिकतम 2 कार्यों के हल्के भार के बाद, पूल सभी थ्रेड का उपयोग करेगा थ्रेड सेवानिवृत्ति को रोकने वाले हल्के लोड को सेवा देने के लिए। (केवल 2 धागे की जरूरत होगी ...)

ऊपर के व्यवहार से नाखुश, मैंने आगे बढ़कर ऊपर की कमियों को दूर करने के लिए एक पूल लागू किया।

2 को हल करने के लिए) लाइफो शेड्यूलिंग का उपयोग समस्या को हल करता है। इस विचार को बेन मौरर ने ACM आवेदन पत्र 2015 के सम्मेलन: सिस्टम @ फेसबुक के पैमाने पर प्रस्तुत किया

इसलिए एक नया कार्यान्वयन पैदा हुआ:

LifoThreadPoolExecutorSQP

अब तक यह कार्यान्वयन ZEL के लिए async निष्पादन पूर्णता में सुधार करता है ।

कार्यान्वयन स्पिन स्विच ओवरहेड को कम करने में सक्षम है, कुछ उपयोग के मामलों के लिए बेहतर प्रदर्शन प्रदान करता है।

आशा करता हूँ की ये काम करेगा...

पुनश्च: JDK Fork पूल कार्यान्वयन ExecutorService में शामिल हों और "सामान्य" थ्रेड पूल के रूप में काम करता है, कार्यान्वयन निष्पादक है, यह LIFO थ्रेड शेड्यूलिंग का उपयोग करता है, हालांकि आंतरिक कतार आकार, सेवानिवृत्ति समय सीमा पर कोई नियंत्रण नहीं है ... और सबसे महत्वपूर्ण कार्य नहीं हो सकते हैं उन्हें रद्द करने पर बाधित हुआ


1
बहुत बुरा है कि इस कार्यान्वयन में बहुत अधिक बाहरी निर्भरताएं हैं। मेरे लिए इसे बेकार बनाना: - /
मार्टिन एल।

1
यह वास्तव में एक अच्छा बिंदु (2) है। दुर्भाग्य से, कार्यान्वयन बाहरी निर्भरता से स्पष्ट नहीं है, लेकिन फिर भी यदि आप चाहें तो इसे अपनाया जा सकता है।
एलेक्सी वाल्लासोव

1

नोट: मैं अब अपने अन्य उत्तर को प्राथमिकता देता हूं और सुझाता हूं

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

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

final Runnable SENTINEL_NO_OP = new Runnable() { public void run() { } };

final AtomicInteger waitingThreads = new AtomicInteger(0);

BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
    @Override
    public boolean offer(Runnable e) {
        // offer returning false will cause the executor to spawn a new thread
        if (e == SENTINEL_NO_OP) return size() <= waitingThreads.get();
        else return super.offer(e);
    }

    @Override
    public Runnable poll(long timeout, TimeUnit unit) throws InterruptedException {
        try {
            waitingThreads.incrementAndGet();
            return super.poll(timeout, unit);
        } finally {
            waitingThreads.decrementAndGet();
        }
    }

    @Override
    public Runnable take() throws InterruptedException {
        try {
            waitingThreads.incrementAndGet();
            return super.take();
        } finally {
            waitingThreads.decrementAndGet();
        }
    }
};

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 50, 60, TimeUnit.SECONDS, queue) {
    @Override
    public void execute(Runnable command) {
        super.execute(command);
        if (getQueue().size() > waitingThreads.get()) super.execute(SENTINEL_NO_OP);
    }
};
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        if (r == SENTINEL_NO_OP) return;
        else throw new RejectedExecutionException();            
    }
});

0

सबसे अच्छा समाधान जो मैं सोच सकता हूं वह है विस्तार करना।

ThreadPoolExecutorकुछ हुक तरीके प्रदान करता है: beforeExecuteऔर afterExecute। अपने विस्तार में आप कार्यों में फीड करने के लिए एक बंधी हुई कतार और ओवरफ्लो को संभालने के लिए दूसरी अनबाउंड कतार का उपयोग कर सकते हैं। जब कोई फोन करता है submit, तो आप अनुरोध को बंधी हुई कतार में रखने का प्रयास कर सकते हैं। यदि आप एक अपवाद के साथ मिले हैं, तो आप कार्य को अपनी अतिप्रवाह कतार में चिपका देते हैं। आप तब afterExecuteहुक का उपयोग करके देख सकते हैं कि कोई कार्य पूरा करने के बाद ओवरफ्लो कतार में कुछ है या नहीं। इस तरह, निष्पादक पहले बंधी हुई कतार में सामान की देखभाल करेगा, और समय की अनुमति के रूप में स्वचालित रूप से इस बिना कतार से खींचेगा।

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


मैं इसके समाधान का पक्षधर नहीं हूं। मुझे पूरा यकीन है कि ThreadPoolExecutor वंशानुक्रम के लिए डिज़ाइन नहीं किया गया था।
scottb

वास्तव में JavaDoc में एक्सटेंशन एक्सटेंशन का एक उदाहरण है। वे कहते हैं कि अधिकांश शायद हुक विधियों को लागू करेंगे, लेकिन वे आपको बताते हैं कि विस्तार करते समय आपको और क्या देखने की आवश्यकता है।
बस्तम्पी

0

नोट: JDK ThreadPoolExecutor के लिए जब आपके पास एक बंधी हुई कतार होती है, तो आप केवल नए थ्रेड्स बना रहे होते हैं, जब ऑफ़र झूठी होती है। आपको CallerRunsPolicy के साथ कुछ उपयोगी मिल सकता है जो कि BackPressure का एक सा बनाता है और सीधे कॉलर थ्रेड में कॉल चलाता है।

मैं कार्यों पूल द्वारा बनाई धागे से मार डाला और शेड्यूलिंग के लिए एक ubounded कतार है करने की आवश्यकता है, जबकि पूल भीतर थ्रेड की संख्या हो सकती है हो जाना या हटना के बीच corePoolSize और maximumPoolSize तो ...

मैंने ThreadPoolExecutor से पूरी कॉपी पेस्ट करने के बाद समाप्त कर दिया और निष्पादन विधि को थोड़ा बदल दिया क्योंकि दुर्भाग्य से यह एक्सटेंशन द्वारा नहीं किया जा सकता था (इसे निजी तरीके कहते हैं)।

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

private final AtomicInteger activeWorkers = new AtomicInteger(0);
private volatile double threshold = 0.7d;

protected void beforeExecute(Thread t, Runnable r) {
    activeWorkers.incrementAndGet();
}
protected void afterExecute(Runnable r, Throwable t) {
    activeWorkers.decrementAndGet();
}
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();

        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }

        if (isRunning(c) && this.workQueue.offer(command)) {
            int recheck = this.ctl.get();
            if (!isRunning(recheck) && this.remove(command)) {
                this.reject(command);
            } else if (workerCountOf(recheck) == 0) {
                this.addWorker((Runnable) null, false);
            }
            //>>change start
            else if (workerCountOf(recheck) < maximumPoolSize //
                && (activeWorkers.get() > workerCountOf(recheck) * threshold
                    || workQueue.size() > workerCountOf(recheck) * threshold)) {
                this.addWorker((Runnable) null, false);
            }
            //<<change end
        } else if (!this.addWorker(command, false)) {
            this.reject(command);
        }
    }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.