यदि यह संतृप्त है, तो थ्रेडपूल एक्ज़ीक्यूटर के सबमिट () विधि ब्लॉक कैसे करें?


102

मैं एक ThreadPoolExecutorऐसा बनाना चाहता हूं कि जब यह अपने अधिकतम आकार तक पहुंच गया हो और कतार पूरी हो गई हो, नए कार्यों को जोड़ने की कोशिश करते समय submit()विधि अवरुद्ध हो जाती है। क्या मुझे उसके लिए एक कस्टम लागू करने की आवश्यकता RejectedExecutionHandlerहै या क्या एक मानक जावा लाइब्रेरी का उपयोग करने का कोई मौजूदा तरीका है?


2
क्या आप कुछ भी चाहते हैं जैसे ऐरे ब्लॉकिंग कतार की पेशकश () विधि है?
extraneon


2
@ बकर मैं असहमत। यह Q & A अधिक मूल्यवान लगता है (पुराने होने के अलावा)।
जेसनमॉर्कर

जवाबों:


47

संभव समाधान मैं पाया है में से एक:

public class BoundedExecutor {
    private final Executor exec;
    private final Semaphore semaphore;

    public BoundedExecutor(Executor exec, int bound) {
        this.exec = exec;
        this.semaphore = new Semaphore(bound);
    }

    public void submitTask(final Runnable command)
            throws InterruptedException, RejectedExecutionException {
        semaphore.acquire();
        try {
            exec.execute(new Runnable() {
                public void run() {
                    try {
                        command.run();
                    } finally {
                        semaphore.release();
                    }
                }
            });
        } catch (RejectedExecutionException e) {
            semaphore.release();
            throw e;
        }
    }
}

क्या कोई और उपाय है? मैं कुछ RejectedExecutionHandlerऐसी परिस्थितियों को संभालने के लिए एक मानक तरीके की तरह लगता है के आधार पर पसंद करेंगे ।


2
क्या इस बिंदु के बीच यहां एक दौड़ की स्थिति है जब अंत में खंड में सेमाफोर जारी किया जाता है और सेमाफोर का अधिग्रहण किया जाता है?
वोलनी

2
जैसा कि ऊपर उल्लेख किया गया है, यह कार्यान्वयन त्रुटिपूर्ण है क्योंकि कार्य पूरा होने से पहले ही सेमीफोर जारी हो जाता है। बेहतर होगा कि java.util.concurrent.ThreadPoolExecutor # afterExecute (Runnable, Throwable)
FelixM

2
@FelixM: java.util.concurrent.ThreadPoolExecutor # afterExecute (Runnable, Throwable) का उपयोग करने से समस्या का समाधान नहीं होगा, क्योंकि afterExecute को task.run () के बाद java.util.concurrent.ThreadPoolExecutor # runWorker (Work w) में रखा जाता है। अगले तत्व को कतार से लेना (ओपनजॉक के स्रोत कोड को देखना 1.7.0.6)।
जान

1
यह उत्तर पुस्तक ब्रायन गोएत्ज़ द्वारा जावा
कंसेंटर

11
यह उत्तर पूरी तरह से सही नहीं है, टिप्पणियाँ भी हैं। कोड का यह टुकड़ा वास्तव में प्रैक्टिस में जावा कंसेंटर से आता है, और यदि आप इसके संदर्भ को ध्यान में रखते हैं तो यह सही है । पुस्तक स्पष्ट रूप से बताती है, "इस तरह के दृष्टिकोण में, एक अनबाउंड क्यू (...) का उपयोग करें और सेमीफोर पर बाध्य को पूल आकार के बराबर सेट करें और जितने कतार वाले कार्यों को आप अनुमति देना चाहते हैं"। एक अनबाउंड कतार के साथ, कार्यों को कभी भी अस्वीकार नहीं किया जाएगा, इसलिए अपवाद को फिर से जोड़ना पूरी तरह से बेकार है! कौन सा है, यह भी कारण है कि है मुझे विश्वास है, throw e;है नहीं किताब में। JCIP सही है!
टिम्मोस ऑग

30

आप ThreadPoolExecutor और एक अवरुद्ध क्यू का उपयोग कर सकते हैं:

public class ImageManager {
    BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<Runnable>(blockQueueSize);
    RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.CallerRunsPolicy();
    private ExecutorService executorService =  new ThreadPoolExecutor(numOfThread, numOfThread, 
        0L, TimeUnit.MILLISECONDS, blockingQueue, rejectedExecutionHandler);

    private int downloadThumbnail(String fileListPath){
        executorService.submit(new yourRunnable());
    }
}

मैं सिर्फ इतना कहना चाहूंगा कि यह एक बहुत ही त्वरित और आसान समाधान है जिसे लागू करने के लिए बहुत अच्छा काम किया है!
इवान

58
यह सबमिट थ्रेड पर अस्वीकृत कार्य चलाता है। जो कार्यात्मक रूप से ओपी की आवश्यकताओं को पूरा नहीं करता है।
धारणा

4
यह कार्य को "कॉलिंग थ्रेड में" चलाता है, इसे कतार में रखने के लिए अवरुद्ध करने के बजाय, जिसके कुछ प्रतिकूल प्रभाव हो सकते हैं, जैसे कि यदि कई थ्रेड इसे इस तरह कहते हैं, तो "कतार आकार" से अधिक कार्य चल रहे होंगे, और यदि कार्य अपेक्षा से अधिक समय तक होता है, आपका "उत्पादन" धागा निष्पादक को व्यस्त नहीं रख सकता है। लेकिन यहाँ बहुत अच्छा काम किया!
रॉगरडैक

4
डाउनवोट: यह TPE के संतृप्त होने पर ब्लॉक नहीं होता है। यह सिर्फ एक विकल्प है, समाधान नहीं है।
टिम्मोस

1
Upvoted: यह बहुत ज्यादा 'TPE के डिजाइन' और 'स्वाभाविक रूप से ब्लॉक' क्लाइंट थ्रेड्स को ओवरफ्लो करने के लिए उन्हें ओवरफ्लो करने के लिए फिट बैठता है। यह अधिकांश उपयोग के मामलों को कवर करना चाहिए, लेकिन उन सभी को निश्चित रूप से नहीं करना चाहिए और आपको यह समझना चाहिए कि हुड के नीचे क्या चल रहा है।
माइक

12

आपको इसका उपयोग करना चाहिए CallerRunsPolicy, जो कॉलिंग थ्रेड में अस्वीकार किए गए कार्य को निष्पादित करता है। इस तरह, यह निष्पादनकर्ता को तब तक कोई नया कार्य प्रस्तुत नहीं कर सकता है जब तक कि वह कार्य पूरा नहीं हो जाता है, जिस बिंदु पर कुछ मुफ्त पूल थ्रेड होंगे या प्रक्रिया दोहराएगी।

http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ThreadPoolExecutor.CallerRunsPolicy.html

डॉक्स से:

अस्वीकृत कार्य

विधि निष्पादन में प्रस्तुत नए कार्य (java.lang.Runnable) को अस्वीकार कर दिया जाएगा जब एक्जिक्यूटर को बंद कर दिया गया है, और यह भी जब एक्जिक्यूटर अधिकतम थ्रेड्स और कार्य कतार क्षमता दोनों के लिए परिमित सीमा का उपयोग करता है, और संतृप्त होता है। या तो मामले में, निष्पादित विधि RejectExecutionHandler.rejectedExecution (java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) विधि को उसके RejectExecutionHandler को आमंत्रित करती है। चार पूर्वनिर्धारित हैंडलर नीतियां प्रदान की जाती हैं:

  1. डिफ़ॉल्ट थ्रेडपूल.एक्सप्यूटर .bortPolicy में, हैंडलर अस्वीकृति पर एक रनटाइम रिजेक्टेक्सेशन एक्ससेप्शन फेंकता है।
  2. ThreadPoolExecutor.all.RererRunsPolicy में, जिस थ्रेड को निष्पादित करता है वह कार्य चलाता है। यह एक सरल प्रतिक्रिया नियंत्रण तंत्र प्रदान करता है जो नए कार्यों को प्रस्तुत करने की दर को धीमा कर देगा।
  3. ThreadPoolExecutor.DiscardPolicy में, एक कार्य जिसे निष्पादित नहीं किया जा सकता है उसे बस छोड़ दिया जाता है।
  4. ThreadPoolExecutor में।

साथ ही, ThreadPoolExecutorकंस्ट्रक्टर को कॉल करते समय एक बंधी हुई कतार, जैसे कि ArrayBlockingQueue, का उपयोग करना सुनिश्चित करें । अन्यथा, कुछ भी अस्वीकार नहीं किया जाएगा।

संपादित करें: अपनी टिप्पणी के जवाब में, ArrayBlockingQueue के आकार को थ्रेड पूल के अधिकतम आकार के बराबर सेट करें और AbortPolicy का उपयोग करें।

संपादन 2: ठीक है, मैं देख रहा हूँ कि तुम क्या कर रहे हो। इस बारे में क्या: यह beforeExecute()जाँचने की विधि को ओवरराइड करें कि getActiveCount()अधिक नहीं है getMaximumPoolSize(), और यदि यह होता है, तो सो जाओ और फिर से कोशिश करो?


3
मैं समवर्ती रूप से निष्पादित कार्यों की संख्या को कड़ाई से बाध्य करना चाहता हूं (एक्जिक्यूटर में थ्रेड्स की संख्या से), यही कारण है कि मैं कॉलर थ्रेड को इन कार्यों को स्वयं निष्पादित करने की अनुमति नहीं दे सकता।
Fixpoint

1
AbortPolicy कॉल करने वाले थ्रेड को रिजेक्टेड एक्सक्लूसिव अपवाद प्राप्त होगा, जबकि मुझे इसे ब्लॉक करने की आवश्यकता है।
फिक्स

2
मैं अवरुद्ध करने के लिए कह रहा हूं, नींद और मतदान नहीं;)
फिक्स प्वाइंट

@danben: क्या आपका मतलब CallerRunsPolicy नहीं है ?
user359996

7
CallerRunPolicy के साथ समस्या यह है कि यदि आपके पास एक भी थ्रेड निर्माता है, तो आपके पास अक्सर ऐसे थ्रेड होंगे जिनका उपयोग नहीं किया जा रहा है यदि लंबे समय तक चलने वाले कार्य को अस्वीकार कर दिया जाता है (क्योंकि थ्रेड पूल में अन्य कार्य समाप्त हो जाएंगे जबकि लंबे समय तक चलने वाला कार्य अभी भी है चल रहा है)।
एडम जेंट

6

सीतनिद्रा में होना एक BlockPolicyसरल है और आप जो चाहते हैं वह कर सकते हैं:

देखें: Executors.java

/**
 * A handler for rejected tasks that will have the caller block until
 * space is available.
 */
public static class BlockPolicy implements RejectedExecutionHandler {

    /**
     * Creates a <tt>BlockPolicy</tt>.
     */
    public BlockPolicy() { }

    /**
     * Puts the Runnable to the blocking queue, effectively blocking
     * the delegating thread until space is available.
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        try {
            e.getQueue().put( r );
        }
        catch (InterruptedException e1) {
            log.error( "Work discarded, thread was interrupted while waiting for space to schedule: {}", r );
        }
    }
}

4
दूसरे विचार पर, यह एक बहुत बुरा विचार है। मैं आपको इसका उपयोग करने की सलाह नहीं देता। अच्छे कारणों के लिए यहां देखें: stackoverflow.com/questions/3446011/…
Nate Murray

ओपी के अनुरोध के अनुसार, यह "मानक जावा पुस्तकालय" का उपयोग नहीं कर रहा है। हटाना चाहते हैं?
user359996

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

1
com.amazonaws.services.simpleworkflow.flow.worker.BlockCallerPolicy समान है।
एड्रियन बेकर

6

BoundedExecutorजवाब से ऊपर उद्धृत अभ्यास में जावा संगामिति केवल ठीक से काम करता है अगर आप निर्वाहक के लिए एक असीम कतार उपयोग करें, या सेमाफोर बाध्य कतार आकार से अधिक नहीं है। सेमाफोर को जमा करने वाले धागे और पूल में धागे के बीच साझा किया जाता है, जिससे निष्पादनकर्ता को संतृप्त करना संभव हो जाता है, भले ही कतार आकार <बाध्य <= (कतार आकार + पूल आकार)।

उपयोग करना CallerRunsPolicyकेवल तभी मान्य है जब आपके कार्य हमेशा के लिए नहीं चलते हैं, जिस स्थिति में आपका सबमिट धागा rejectedExecutionहमेशा के लिए बना रहेगा, और एक बुरा विचार यदि आपके कार्यों को चलाने में लंबा समय लगता है, क्योंकि सबमिट करने वाला धागा कोई भी नया कार्य सबमिट नहीं कर सकता है या कुछ भी करो अगर यह एक कार्य ही चल रहा है।

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


मुझे यकीन नहीं है कि कई कार्य उत्पादकों के साथ बहु-थ्रेडेड वातावरण में अस्वीकार किए गए कार्यों की गारंटी देने से पहले कतार की लंबाई की जांच कैसे की जाती है। यह थ्रेड-साउंड सुरक्षित नहीं है।
टिम

5

मुझे पता है, यह एक हैक है, लेकिन मेरी राय में यहाँ की पेशकश के बीच सबसे साफ हैक ;-)

क्योंकि ThreadPoolExecutor "put" के बजाय अवरुद्ध कतार "ऑफ़र" का उपयोग करता है, अवरुद्ध अवरोधक के "ऑफ़र" के व्यवहार को ओवरराइड करता है:

class BlockingQueueHack<T> extends ArrayBlockingQueue<T> {

    BlockingQueueHack(int size) {
        super(size);
    }

    public boolean offer(T task) {
        try {
            this.put(task);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return true;
    }
}

ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 2, 1, TimeUnit.MINUTES, new BlockingQueueHack(5));

मैंने इसका परीक्षण किया और यह काम करने लगता है। कुछ टाइमआउट पॉलिसी को लागू करना एक पाठक की कवायद के रूप में शेष है।


इसके एक साफ किए गए संस्करण के लिए stackoverflow.com/a/4522411/2601671 देखें । मैं मानता हूं, यह सबसे साफ तरीका है।
ट्रेंटन

3

निम्न वर्ग एक थ्रेडपूल एक्सक्यूसर के चारों ओर लपेटता है और फिर काम की कतार पूरी होने पर ब्लॉक करने के लिए एक सेमाफोर का उपयोग करता है:

public final class BlockingExecutor { 

    private final Executor executor;
    private final Semaphore semaphore;

    public BlockingExecutor(int queueSize, int corePoolSize, int maxPoolSize, int keepAliveTime, TimeUnit unit, ThreadFactory factory) {
        BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
        this.executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, unit, queue, factory);
        this.semaphore = new Semaphore(queueSize + maxPoolSize);
    }

    private void execImpl (final Runnable command) throws InterruptedException {
        semaphore.acquire();
        try {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        command.run();
                    } finally {
                        semaphore.release();
                    }
                }
            });
        } catch (RejectedExecutionException e) {
            // will never be thrown with an unbounded buffer (LinkedBlockingQueue)
            semaphore.release();
            throw e;
        }
    }

    public void execute (Runnable command) throws InterruptedException {
        execImpl(command);
    }
}

यह रैपर क्लास ब्रायन गोएट्ज द्वारा प्रैक्टिस में जावा कॉन्सेप्टरी नामक पुस्तक में दिए गए समाधान पर आधारित है। पुस्तक में समाधान केवल दो निर्माण मापदंडों को लेता है: एक Executorऔर एक अर्धवृत्त के लिए उपयोग किया जाता है। इसे Fixpoint द्वारा दिए गए उत्तर में दिखाया गया है। उस दृष्टिकोण के साथ एक समस्या है: यह एक ऐसी स्थिति में मिल सकता है जहां पूल धागे व्यस्त हैं, कतार भरी हुई है, लेकिन सेमीफोर ने सिर्फ एक परमिट जारी किया है। ( semaphore.release()अंत में ब्लॉक में)। इस स्थिति में, एक नया कार्य केवल जारी किए गए परमिट को पकड़ सकता है, लेकिन अस्वीकार कर दिया जाता है क्योंकि कार्य कतार पूर्ण है। बेशक यह कुछ ऐसा नहीं है जो आप चाहते हैं; आप इस मामले में ब्लॉक करना चाहते हैं।

इस समस्या के समाधान के लिए, हम एक का उपयोग करना चाहिए असीम रूप JCIP स्पष्ट रूप से उल्लेख है, कतार। अर्धकुंभ एक आभासी कतार आकार का प्रभाव देते हुए, एक गार्ड के रूप में कार्य करता है। इसका साइड इफेक्ट है कि यह संभव है कि यूनिट में maxPoolSize + virtualQueueSize + maxPoolSizeकार्य हो सकते हैं । ऐसा क्यों है? क्योंकि semaphore.release()आखिरकार ब्लॉक में। यदि सभी पूल थ्रेड्स एक ही समय में इस स्टेटमेंट को कॉल करते हैं, तो maxPoolSizeपरमिट जारी किए जाते हैं, जिससे यूनिट में समान कार्य करने की अनुमति मिलती है। यदि हम एक बंधी हुई कतार का उपयोग कर रहे हैं, तो यह अभी भी पूर्ण होगा, जिसके परिणामस्वरूप अस्वीकृत कार्य होगा। अब, क्योंकि हम जानते हैं कि यह केवल तब होता है जब पूल थ्रेड लगभग किया जाता है, यह कोई समस्या नहीं है। हम जानते हैं कि पूल थ्रेड ब्लॉक नहीं होगा, इसलिए जल्द ही एक कार्य कतार से लिया जाएगा।

आप हालांकि एक बंधी हुई कतार का उपयोग करने में सक्षम हैं। बस यह सुनिश्चित करें कि इसका आकार बराबर हो virtualQueueSize + maxPoolSize। ग्रेटर आकार बेकार हैं, सेमाफोर अधिक वस्तुओं को अंदर जाने से रोकेगा। छोटे आकार के परिणामस्वरूप अस्वीकृत कार्य होंगे। आकार घटने के साथ कार्य अस्वीकार होने की संभावना बढ़ जाती है। उदाहरण के लिए, मान लें कि आप एक अधिकतम निष्पादक चाहते हैं जो maxPoolSize = 2 और virtualQueueSize = 5 के साथ है। फिर 5 + 2 = 7 परमिट और 5 + 2 = 7 के वास्तविक कतार आकार के साथ एक सेमाफोर लें। यूनिट में होने वाले कार्यों की वास्तविक संख्या 2 + 5 + 2 = 9 है। जब निष्पादक पूर्ण होता है (कतार में 5 कार्य, थ्रेड पूल में 2, तो 0 परमिट उपलब्ध होते हैं) और सभी पूल थ्रेड अपने परमिट जारी करते हैं, तो ठीक 2 परमिट में आने वाले कार्यों द्वारा लिया जा सकता है।

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


2

आप इस तरह एक कस्टम RejectExecutionHandler का उपयोग कर सकते हैं

ThreadPoolExecutor tp= new ThreadPoolExecutor(core_size, // core size
                max_handlers, // max size 
                timeout_in_seconds, // idle timeout 
                TimeUnit.SECONDS, queue, new RejectedExecutionHandler() {
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        // This will block if the queue is full
                        try {
                            executor.getQueue().put(r);
                        } catch (InterruptedException e) {
                            System.err.println(e.getMessage());
                        }

                    }
                });

1
GetQueue () के लिए डॉक्स स्पष्ट रूप से उल्लेख करते हैं कि कार्य कतार तक पहुंच मुख्य रूप से डिबगिंग और निगरानी के लिए है।
चाडी

0

एक्सेक्यूटर द्वारा उपयोग की जाने वाली अपनी स्वयं की अवरुद्ध कतार बनाएं, जिस अवरोधक व्यवहार की तलाश कर रहे हैं, जबकि हमेशा उपलब्ध शेष क्षमता को वापस करना (यह सुनिश्चित करना कि निष्पादक अपने मुख्य पूल की तुलना में अधिक थ्रेड बनाने की कोशिश नहीं करेगा, या अस्वीकृति हैंडलर को ट्रिगर करेगा)।

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


2
ThreadPoolExecutorofferकार्यों को कतार में जोड़ने के लिए विधि का उपयोग करता है । अगर मैंने एक ऐसा रिवाज बनाया है BlockingQueueजो ब्लॉक करता है offer, तो वह BlockingQueueअनुबंध को तोड़ देगा ।
फिक्स पॉइंट

@Soshpanchick, जो ब्लॉकिंग क्यू अनुबंधों को तोड़ देगा। तो क्या? यदि आप इतने उत्सुक हैं, तो आप स्पष्ट रूप से प्रस्तुत करने के दौरान व्यवहार को सक्षम कर सकते हैं () केवल (यह एक
थ्रेडलोक

एक अन्य प्रश्न का यह उत्तर भी देखें जो इस विकल्प को बताता है।
रॉबर्ट टुपेलो-श्नेक

क्या कोई ऐसा कारण है ThreadPoolExecutorजिसे उपयोग करने के लिए लागू किया गया था offerऔर नहीं put(अवरुद्ध संस्करण)? इसके अलावा, अगर क्लाइंट कोड के लिए यह बताने का कोई तरीका है कि कब,
किसका

0

@FixPoint समाधान के साथ समस्याओं से बचने के लिए। एक ListeningExecutorService का उपयोग कर सकता है और भविष्यफल के अंदर अर्ध-असफलता और असफलता जारी कर सकता है।


इसमें वही अंतर्निहित समस्याएं हैं जैसे Runnableकि उन तरीकों को लपेटना जो अभी भी सामान्य में श्रमिक सफाई से पहले कहे जाते हैं ThreadPoolExecutor। यही कारण है कि आपको अभी भी अस्वीकृति अपवादों को संभालने की आवश्यकता होगी।
एडम गेंट

0

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

सभी जवाबों और टिप्पणियों को पढ़ना, विशेष रूप से सेमाफोर के साथ त्रुटिपूर्ण समाधान या afterExecuteमेरे उपयोग करने के लिए थ्रेडपूल एक्सिक्यूटर के कोड पर एक करीब से देखने के लिए कि क्या कोई रास्ता है। मुझे यह देखकर आश्चर्य हुआ कि (टिप्पणी) कोड की 2000 से अधिक लाइनें हैं, जिनमें से कुछ मुझे चक्कर महसूस करवाती हैं । वास्तव में सरल आवश्यकता को देखते हुए मेरे पास --- एक निर्माता, कई उपभोक्ता, निर्माता को ब्लॉक होने देते हैं जब कोई भी उपभोक्ता काम नहीं कर सकता है --- मैंने अपना स्वयं का समाधान रोल करने का फैसला किया। यह एक नहीं ExecutorServiceबल्कि सिर्फ एक है Executor। और यह थ्रेड्स की संख्या को कार्य भार के अनुकूल नहीं करता है, लेकिन केवल थ्रेड्स की एक निश्चित संख्या रखता है, जो मेरी आवश्यकताओं को भी पूरा करता है। यहाँ कोड है। इसके बारे में शेख़ी के लिए स्वतंत्र महसूस करें :-)

package x;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;

/**
 * distributes {@code Runnable}s to a fixed number of threads. To keep the
 * code lean, this is not an {@code ExecutorService}. In particular there is
 * only very simple support to shut this executor down.
 */
public class ParallelExecutor implements Executor {
  // other bounded queues work as well and are useful to buffer peak loads
  private final BlockingQueue<Runnable> workQueue =
      new SynchronousQueue<Runnable>();
  private final Thread[] threads;

  /*+**********************************************************************/
  /**
   * creates the requested number of threads and starts them to wait for
   * incoming work
   */
  public ParallelExecutor(int numThreads) {
    this.threads = new Thread[numThreads];
    for(int i=0; i<numThreads; i++) {
      // could reuse the same Runner all over, but keep it simple
      Thread t = new Thread(new Runner());
      this.threads[i] = t;
      t.start();
    }
  }
  /*+**********************************************************************/
  /**
   * returns immediately without waiting for the task to be finished, but may
   * block if all worker threads are busy.
   * 
   * @throws RejectedExecutionException if we got interrupted while waiting
   *         for a free worker
   */
  @Override
  public void execute(Runnable task)  {
    try {
      workQueue.put(task);
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new RejectedExecutionException("interrupt while waiting for a free "
          + "worker.", e);
    }
  }
  /*+**********************************************************************/
  /**
   * Interrupts all workers and joins them. Tasks susceptible to an interrupt
   * will preempt their work. Blocks until the last thread surrendered.
   */
  public void interruptAndJoinAll() throws InterruptedException {
    for(Thread t : threads) {
      t.interrupt();
    }
    for(Thread t : threads) {
      t.join();
    }
  }
  /*+**********************************************************************/
  private final class Runner implements Runnable {
    @Override
    public void run() {
      while (!Thread.currentThread().isInterrupted()) {
        Runnable task;
        try {
          task = workQueue.take();
        } catch (InterruptedException e) {
          // canonical handling despite exiting right away
          Thread.currentThread().interrupt(); 
          return;
        }
        try {
          task.run();
        } catch (RuntimeException e) {
          // production code to use a logging framework
          e.printStackTrace();
        }
      }
    }
  }
}

0

मेरा मानना ​​है कि java.util.concurrent.Semaphoreव्यवहार के उपयोग और प्रतिनिधि द्वारा इस समस्या को हल करने का काफी सुरुचिपूर्ण तरीका है Executor.newFixedThreadPool। नई निष्पादक सेवा केवल तब नए कार्य को अंजाम देगी जब ऐसा करने के लिए एक सूत्र होगा। ब्लॉकिंग का प्रबंधन सेमीफोर द्वारा धागों की संख्या के बराबर परमिटों की संख्या के साथ किया जाता है। जब कोई कार्य पूरा हो जाता है तो वह परमिट देता है।

public class FixedThreadBlockingExecutorService extends AbstractExecutorService {

private final ExecutorService executor;
private final Semaphore blockExecution;

public FixedThreadBlockingExecutorService(int nTreads) {
    this.executor = Executors.newFixedThreadPool(nTreads);
    blockExecution = new Semaphore(nTreads);
}

@Override
public void shutdown() {
    executor.shutdown();
}

@Override
public List<Runnable> shutdownNow() {
    return executor.shutdownNow();
}

@Override
public boolean isShutdown() {
    return executor.isShutdown();
}

@Override
public boolean isTerminated() {
    return executor.isTerminated();
}

@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
    return executor.awaitTermination(timeout, unit);
}

@Override
public void execute(Runnable command) {
    blockExecution.acquireUninterruptibly();
    executor.execute(() -> {
        try {
            command.run();
        } finally {
            blockExecution.release();
        }
    });
}

मैंने जावा कॉन्सेरिटी में प्रैक्टिस में वर्णित बाउंडेड एक्सिक्यूटर को लागू किया और पता लगाया कि सेमफोर को फेयरनेस फ्लैग के साथ शुरू किया जाना चाहिए ताकि यह सुनिश्चित हो सके कि सेमीफोर परमिट ऑर्डर में किए गए हैं। Docs.oracle.com/javase/7/docs/api/java/util/concurrent/… देखें । विवरण के लिए
प्रहलाद देशपांडे

0

मुझे अतीत में एक ही आवश्यकता थी: एक साझा थ्रेड पूल द्वारा समर्थित प्रत्येक क्लाइंट के लिए निश्चित आकार के साथ एक प्रकार की अवरुद्ध कतार। मैंने अपने खुद के थ्रेडपूल एक्सक्यूसॉर लिखने का काम खत्म किया:

UserThreadPoolExecutor (प्रति ग्राहक अवरुद्ध करना) + थ्रेडपूल (सभी क्लाइंट्स के बीच साझा किया गया)

देखें: https://github.com/d4rxh4wx/UserThreadPoolExecutor

प्रत्येक UserThreadPoolExecutor को एक साझा ThreadPoolExecutor से अधिकतम संख्या में थ्रेड दिए जाते हैं

प्रत्येक UserThreadPoolExecutor कर सकते हैं:

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

0

मुझे यह अस्वीकृति नीति लोचदार खोज क्लाइंट में मिली। यह ब्लॉकिंग कतार पर कॉलर थ्रेड को ब्लॉक करता है। नीचे कोड-

 static class ForceQueuePolicy implements XRejectedExecutionHandler 
 {
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) 
        {
            try 
            {
                executor.getQueue().put(r);
            } 
            catch (InterruptedException e) 
            {
                //should never happen since we never wait
                throw new EsRejectedExecutionException(e);
            }
        }

        @Override
        public long rejected() 
        {
            return 0;
        }
}

0

मुझे हाल ही में कुछ समान हासिल करने की आवश्यकता थी, लेकिन ए ScheduledExecutorService

मुझे यह भी सुनिश्चित करना था कि मैं विधि पर पारित होने में देरी को संभालता हूं और यह सुनिश्चित करता हूं कि या तो कार्य को समय पर निष्पादित करने के लिए प्रस्तुत किया जाए क्योंकि कॉल करने वाले को उम्मीद है या इस प्रकार असफल हो जाता है RejectedExecutionException

से अन्य तरीके ScheduledThreadPoolExecutorकिसी कार्य को आंतरिक रूप से कॉल करने या प्रस्तुत करने के#schedule जो अभी भी ओवरराइड किए गए तरीकों को लागू करते हैं।

import java.util.concurrent.*;

public class BlockingScheduler extends ScheduledThreadPoolExecutor {
    private final Semaphore maxQueueSize;

    public BlockingScheduler(int corePoolSize,
                             ThreadFactory threadFactory,
                             int maxQueueSize) {
        super(corePoolSize, threadFactory, new AbortPolicy());
        this.maxQueueSize = new Semaphore(maxQueueSize);
    }

    @Override
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(command, unit.toMillis(delay));
        return super.schedule(command, newDelayInMs, TimeUnit.MILLISECONDS);
    }

    @Override
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay,
                                           TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(callable, unit.toMillis(delay));
        return super.schedule(callable, newDelayInMs, TimeUnit.MILLISECONDS);
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(command, unit.toMillis(initialDelay));
        return super.scheduleAtFixedRate(command, newDelayInMs, unit.toMillis(period), TimeUnit.MILLISECONDS);
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long period,
                                                     TimeUnit unit) {
        final long newDelayInMs = beforeSchedule(command, unit.toMillis(initialDelay));
        return super.scheduleWithFixedDelay(command, newDelayInMs, unit.toMillis(period), TimeUnit.MILLISECONDS);
    }

    @Override
    protected void afterExecute(Runnable runnable, Throwable t) {
        super.afterExecute(runnable, t);
        try {
            if (t == null && runnable instanceof Future<?>) {
                try {
                    ((Future<?>) runnable).get();
                } catch (CancellationException | ExecutionException e) {
                    t = e;
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt(); // ignore/reset
                }
            }
            if (t != null) {
                System.err.println(t);
            }
        } finally {
            releaseQueueUsage();
        }
    }

    private long beforeSchedule(Runnable runnable, long delay) {
        try {
            return getQueuePermitAndModifiedDelay(delay);
        } catch (InterruptedException e) {
            getRejectedExecutionHandler().rejectedExecution(runnable, this);
            return 0;
        }
    }

    private long beforeSchedule(Callable callable, long delay) {
        try {
            return getQueuePermitAndModifiedDelay(delay);
        } catch (InterruptedException e) {
            getRejectedExecutionHandler().rejectedExecution(new FutureTask(callable), this);
            return 0;
        }
    }

    private long getQueuePermitAndModifiedDelay(long delay) throws InterruptedException {
        final long beforeAcquireTimeStamp = System.currentTimeMillis();
        maxQueueSize.tryAcquire(delay, TimeUnit.MILLISECONDS);
        final long afterAcquireTimeStamp = System.currentTimeMillis();
        return afterAcquireTimeStamp - beforeAcquireTimeStamp;
    }

    private void releaseQueueUsage() {
        maxQueueSize.release();
    }
}

मेरे पास यहां कोड है, किसी भी प्रतिक्रिया की सराहना करेंगे। https://github.com/AmitabhAwasthi/BlockingScheduler


यह उत्तर बाहरी लिंक की सामग्री पर पूरी तरह से निर्भर करता है। क्या उन्हें कभी अमान्य हो जाना चाहिए, आपका जवाब बेकार होगा। तो कृपया अपने उत्तर को संपादित करें और कम से कम एक सारांश जोड़ें जो वहां पाया जा सकता है। धन्यवाद!
फैबियो का कहना है कि

@ फैबियो: इशारा करने के लिए धन्यवाद। मैंने इसमें कोड जोड़ा है ताकि अब यह पाठकों के लिए अधिक समझ में आए। अपनी टिप्पणी की सराहना करें :)
देव अमिताभ

0

यहाँ समाधान है कि वास्तव में अच्छी तरह से काम करने लगता है। इसे NotifyingBlockingThreadPoolExecutor कहा जाता है ।

डेमो कार्यक्रम।

संपादित करें: इस कोड के साथ एक समस्या है , प्रतीक्षा () विधि छोटी है। शटडाउन कॉलिंग () + प्रतीक्षाटर्मेशन () ठीक काम करने लगता है।


0

मुझे हमेशा CallerRunsPolicy पसंद नहीं है, खासकर क्योंकि यह अस्वीकार किए गए कार्य को 'कतार छोड़ें' और पहले प्रस्तुत किए गए कार्यों से पहले निष्पादित करने की अनुमति देता है। इसके अलावा, कॉलिंग थ्रेड पर कार्य को निष्पादित करने में पहले स्लॉट के उपलब्ध होने की प्रतीक्षा करने में अधिक समय लग सकता है।

मैंने एक रिजेक्टेड एक्सिकिशनहैंडलर का उपयोग करके इस समस्या को हल किया, जो कॉलिंग थ्रेड को थोड़ी देर के लिए ब्लॉक कर देता है और फिर कार्य को फिर से सबमिट करने की कोशिश करता है:

public class BlockWhenQueueFull implements RejectedExecutionHandler {

    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {

        // The pool is full. Wait, then try again.
        try {
            long waitMs = 250;
            Thread.sleep(waitMs);
        } catch (InterruptedException interruptedException) {}

        executor.execute(r);
    }
}

इस वर्ग को केवल थ्रेड-पूल निष्पादक के रूप में किसी भी अन्य की तरह RejectExecutinHandler के रूप में इस्तेमाल किया जा सकता है:

executorPool = new ThreadPoolExecutor(1, 1, 10,
                                      TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
                                      new BlockWhenQueueFull());

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

फिर भी, मैं व्यक्तिगत रूप से इस पद्धति को पसंद करता हूं। यह कॉम्पैक्ट है, समझने में आसान है, और अच्छी तरह से काम करता है।


1
जैसा कि आप स्वयं कहते हैं: यह स्टैकओवरफ़्लो बना सकता है। ऐसा कुछ नहीं जो मैं प्रोडक्शन कोड में रखना चाहूंगा।
हेराल्ड

हर किसी को अपने फैसले खुद करने चाहिए। मेरे कार्यभार के लिए, यह कोई मुद्दा नहीं है। स्टैक को उड़ाने के लिए आवश्यक घंटों के बजाय कुछ सेकंड में कार्य चलता है। इसके अलावा, एक ही लगभग किसी भी पुनरावर्ती एल्गोरिथ्म के लिए कहा जा सकता है। कि उत्पादन में किसी भी पुनरावर्ती एल्गोरिथ्म का उपयोग नहीं करने का एक कारण है?
टिंकरटैंक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.