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