आइए newWorkStealingPool मेथड को समझते हैं, जो हमारे लिए ExecutorService तैयार करता है।

यह थ्रेड पूल खास है। इसका व्यवहार "चोरी" कार्य के विचार पर आधारित है।

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

कांटा / शामिल हों

फोर्क / जॉइन फ्रेमवर्क में , कार्यों को पुनरावर्ती रूप से विघटित किया जाता है, अर्थात, उन्हें उप-कार्यों में विभाजित किया जाता है। फिर उप-कार्यों को व्यक्तिगत रूप से निष्पादित किया जाता है, और उप-कार्यों के परिणामों को मूल कार्य के परिणाम के रूप में संयोजित किया जाता है।

कांटा विधि कुछ थ्रेड पर अतुल्यकालिक रूप से कार्य शुरू करती है, और जुड़ने की विधि आपको इस कार्य के समाप्त होने की प्रतीक्षा करने देती है

newworkstealingpool

NewWorkStealingPool पद्धति के दो कार्यान्वयन हैं:

public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }

शुरुआत से, हम ध्यान दें कि हुड के तहत हम थ्रेडपूल एक्ज़ीक्यूटर कंस्ट्रक्टर को नहीं बुला रहे हैं। यहां हम ForkJoinPool एंटिटी के साथ काम कर रहे हैं। थ्रेडपूल एक्ज़ीक्यूटर की तरह , यह एब्सट्रैक्ट एक्ज़ीक्यूटर सर्विस का कार्यान्वयन है ।

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

यह पता लगाना बाकी है कि यह व्यवहार में कैसे काम करता है:

Collection<Callable<Void>> tasks = new ArrayList<>();
        ExecutorService executorService = Executors.newWorkStealingPool(10);

        for (int i = 0; i < 10; i++) {
            int taskNumber = i;
            Callable<Void> callable = () -> {
                System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
                return null;
            };
            tasks.add(callable);
        }
        executorService.invokeAll(tasks);

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

पूल में 10 थ्रेड्स पर 10 कार्य निष्पादित करते समय परिणाम:

ForkJoinPool-1-worker-10 थ्रेड पर संसाधित उपयोगकर्ता अनुरोध #9
ForkJoinPool-1-worker-5 थ्रेड पर
संसाधित उपयोगकर्ता अनुरोध #4 ForkJoinPool-1-worker-8 थ्रेड पर संसाधित उपयोगकर्ता अनुरोध #7
ForkJoinPool- पर संसाधित उपयोगकर्ता अनुरोध #1
ForkJoinPool-1- worker-3 थ्रेड पर 1-वर्कर-2 थ्रेड संसाधित उपयोगकर्ता अनुरोध #2
ForkJoinPool-1-वर्कर-4 थ्रेड पर संसाधित उपयोगकर्ता अनुरोध #3 ForkJoinPool-1-वर्कर-4 थ्रेड पर
संसाधित उपयोगकर्ता अनुरोध #6 ForkJoinPool-1-वर्कर-7 थ्रेड
संसाधित उपयोगकर्ता ForkJoinPool-1-worker-1 थ्रेड पर अनुरोध #0 ForkJoinPool-1-worker-1 थ्रेड
पर संसाधित उपयोगकर्ता अनुरोध #5 ForkJoinPool-1-worker-6 थ्रेड पर
संसाधित उपयोगकर्ता अनुरोध #8 ForkJoinPool-1-worker-9 थ्रेड पर

हम देखते हैं कि कतार बनने के बाद, धागे निष्पादन के लिए कार्य करते हैं। आप यह भी देख सकते हैं कि 10 धागों के पूल में 20 कार्यों को कैसे वितरित किया जाएगा।

संसाधित उपयोगकर्ता अनुरोध #3 ForkJoinPool-1-कार्यकर्ता-4 थ्रेड
पर संसाधित उपयोगकर्ता अनुरोध #7 ForkJoinPool-1-कार्यकर्ता-8 थ्रेड
पर संसाधित उपयोगकर्ता अनुरोध #2 ForkJoinPool-1-कार्यकर्ता-3 थ्रेड पर
संसाधित उपयोगकर्ता अनुरोध #4 ForkJoinPool पर-
ForkJoinPool-1-worker-2 थ्रेड पर 1-वर्कर -5 थ्रेड
संसाधित उपयोगकर्ता अनुरोध #1 ForkJoinPool-1-वर्कर-6 थ्रेड पर
संसाधित उपयोगकर्ता अनुरोध #5 ForkJoinPool-1-वर्कर-9 थ्रेड
संसाधित उपयोगकर्ता पर संसाधित उपयोगकर्ता अनुरोध #8 ForkJoinPool-1-worker-10 थ्रेड पर अनुरोध #9
ForkJoinPool-1-worker-1 थ्रेड पर संसाधित उपयोगकर्ता अनुरोध #0
ForkJoinPool-1-worker-1 थ्रेड पर संसाधित उपयोगकर्ता अनुरोध #6 ForkJoinPool-1-worker-7 थ्रेड
संसाधित उपयोगकर्ता अनुरोध #10 ForkJoinPool-1- पर कार्यकर्ता -9 धागा
ForkJoinPool-1-worker-1 थ्रेड पर संसाधित उपयोगकर्ता अनुरोध #12
ForkJoinPool-1-worker-1 थ्रेड पर संसाधित उपयोगकर्ता अनुरोध #13 ForkJoinPool-1-worker-8 थ्रेड पर
संसाधित उपयोगकर्ता अनुरोध #11 ForkJoinPool-1-worker-6 थ्रेड पर
संसाधित उपयोगकर्ता अनुरोध #15 ForkJoinPool- पर ForkJoinPool -1-worker-1 थ्रेड पर 1-वर्कर-8 थ्रेड
प्रोसेस्ड यूजर रिक्वेस्ट #14
ForkJoinPool-1-वर्कर-1 थ्रेड पर प्रोसेस्ड यूजर रिक्वेस्ट #17 ForkJoinPool-1-वर्कर-6 थ्रेड
प्रोसेस्ड यूजर रिक्वेस्ट #16 ForkJoinPool-1-वर्कर-7 थ्रेड
प्रोसेस्ड यूजर ForkJoinPool-1-worker-6 थ्रेड पर अनुरोध #19
ForkJoinPool-1-worker-1 थ्रेड पर संसाधित उपयोगकर्ता अनुरोध #18

आउटपुट से, हम देख सकते हैं कि कुछ थ्रेड्स कई कार्यों को पूरा करने का प्रबंधन करते हैं ( ForkJoinPool-1-worker-6 ने 4 कार्य पूरे किए), जबकि कुछ केवल एक को पूरा करते हैं ( ForkJoinPool-1-worker-2 )। यदि कॉल विधि के कार्यान्वयन में 1-सेकंड की देरी जोड़ी जाती है , तो तस्वीर बदल जाती है।

Callable<Void> callable = () -> {
   System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
   TimeUnit.SECONDS.sleep(1);
   return null;
};

प्रयोग के लिए, उसी कोड को दूसरी मशीन पर चलाते हैं। परिणामी आउटपुट:

ForkJoinPool-1-worker-23 थ्रेड पर संसाधित उपयोगकर्ता अनुरोध #2
ForkJoinPool-1-worker-31 थ्रेड पर
संसाधित उपयोगकर्ता अनुरोध #7 ForkJoinPool-1-worker-27 थ्रेड पर संसाधित उपयोगकर्ता अनुरोध #4 ForkJoinPool
पर संसाधित उपयोगकर्ता अनुरोध #5- ForkJoinPool- 1-worker-19 थ्रेड पर 1-वर्कर-13 थ्रेड
प्रोसेस्ड यूजर रिक्वेस्ट #0
ForkJoinPool-1-वर्कर-3 थ्रेड पर
प्रोसेस्ड यूजर रिक्वेस्ट #8 ForkJoinPool-1-वर्कर-21 थ्रेड प्रोसेस्ड यूजर पर
प्रोसेस्ड यूजर रिक्वेस्ट #9 ForkJoinPool-1-worker-17 थ्रेड पर अनुरोध #6
ForkJoinPool-1-worker-9 थ्रेड पर संसाधित उपयोगकर्ता अनुरोध #3
ForkJoinPool-1-worker-5 थ्रेड पर संसाधित उपयोगकर्ता अनुरोध #1
ForkJoinPool-1- पर संसाधित उपयोगकर्ता अनुरोध #12 कार्यकर्ता -23 धागा
ForkJoinPool-1-worker-19 थ्रेड पर संसाधित उपयोगकर्ता
अनुरोध #15 ForkJoinPool-1-worker-27 थ्रेड पर
संसाधित उपयोगकर्ता अनुरोध #14 ForkJoinPool-1-worker-3 थ्रेड पर संसाधित उपयोगकर्ता अनुरोध #11 ForkJoinPool
पर संसाधित उपयोगकर्ता अनुरोध #13-
ForkJoinPool-1-worker-31 थ्रेड पर 1-वर्कर-13 थ्रेड प्रोसेस्ड यूजर रिक्वेस्ट #
10 ForkJoinPool-1-वर्कर-5 थ्रेड पर
प्रोसेस्ड यूजर रिक्वेस्ट #18 ForkJoinPool-1-वर्कर-9 थ्रेड पर प्रोसेस्ड यूजर रिक्वेस्ट #16 थ्रेड
प्रोसेस्ड यूजर ForkJoinPool-1-worker-21 थ्रेड पर अनुरोध #17
ForkJoinPool-1-worker-17 थ्रेड पर संसाधित उपयोगकर्ता अनुरोध #19

इस आउटपुट में, यह उल्लेखनीय है कि हमने पूल में थ्रेड्स "मांगे"। क्या अधिक है, कार्यकर्ता धागे के नाम एक से दस तक नहीं जाते हैं, बल्कि कभी-कभी दस से अधिक होते हैं। अद्वितीय नामों को देखते हुए, हम देखते हैं कि वास्तव में दस कर्मचारी (3, 5, 9, 13, 17, 19, 21, 23, 27 और 31) हैं। यहां यह पूछना काफी वाजिब है कि ऐसा क्यों हुआ? जब भी आप समझ नहीं पा रहे हैं कि क्या हो रहा है, डीबगर का उपयोग करें।

हम यही करेंगे। चलो कास्ट करते हैंनिष्पादकसेवाForkJoinPool पर आपत्ति करें :

final ForkJoinPool forkJoinPool = (ForkJoinPool) executorService;

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

हम देख सकते हैं कि पूल में 10 धागे हैं, लेकिन वर्कर थ्रेड्स की सरणी का आकार 32 है। अजीब है, लेकिन ठीक है। चलो खोदते रहो। एक पूल बनाते समय, आइए समानता स्तर को 32 से अधिक, मान लें 40 पर सेट करने का प्रयास करें।

ExecutorService executorService = Executors.newWorkStealingPool(40);

आइए डिबगर में देखेंforkJoinPool ऑब्जेक्ट फिर से।

अब वर्कर थ्रेड्स की सरणी का आकार 128 है। हम मान सकते हैं कि यह JVM के आंतरिक अनुकूलन में से एक है। आइए इसे JDK (openjdk-14) के कोड में खोजने का प्रयास करें:

जैसा कि हमें संदेह था: वर्कर थ्रेड्स के ऐरे के आकार की गणना समानता मूल्य पर बिटवाइज़ जोड़तोड़ करके की जाती है। हमें यह पता लगाने की कोशिश करने की ज़रूरत नहीं है कि यहाँ वास्तव में क्या हो रहा है। यह जानना पर्याप्त है कि ऐसा अनुकूलन मौजूद है।

हमारे उदाहरण का एक और दिलचस्प पहलू है इनवोकअल विधि का उपयोग । यह ध्यान देने योग्य है कि इनवोकअल विधि परिणाम वापस कर सकती है, या बल्कि परिणामों की एक सूची (हमारे मामले में, एक सूची <भविष्य <शून्य>>) , जहां हम प्रत्येक कार्य का परिणाम पा सकते हैं।

var results = executorService.invokeAll(tasks);
        for (Future<Void> result : results) {
            // Process the task's result
        }

इस विशेष प्रकार की सेवा और थ्रेड पूल का उपयोग अनुमानित, या कम से कम निहित, संगामिति के स्तर वाले कार्यों में किया जा सकता है।