चला नवीन WorkStealingPool पद्धत शोधून काढू, जी आमच्यासाठी ExecutorService तयार करते.

हा धागा पूल खास आहे. त्याचे वर्तन "चोरी" कामाच्या कल्पनेवर आधारित आहे.

कार्ये रांगेत आणि प्रोसेसरमध्ये वितरीत केली जातात. परंतु जर प्रोसेसर व्यस्त असेल तर दुसरा फ्री प्रोसेसर त्यातून एखादे काम चोरून ते कार्यान्वित करू शकतो. मल्टी-थ्रेडेड ऍप्लिकेशन्समधील संघर्ष कमी करण्यासाठी हे स्वरूप Java मध्ये सादर केले गेले. हे फोर्क/जॉईन फ्रेमवर्कवर तयार केले आहे.

काटा/सामील

फोर्क/जॉइन फ्रेमवर्कमध्ये , कार्ये वारंवार विघटित केली जातात, म्हणजेच ती उपकार्यांमध्ये मोडली जातात. नंतर सबटास्क स्वतंत्रपणे अंमलात आणले जातात आणि सबटास्कचे परिणाम मूळ कार्याचा परिणाम तयार करण्यासाठी एकत्र केले जातात.

फोर्क पद्धत काही थ्रेडवर असिंक्रोनसपणे कार्य सुरू करते आणि जॉइन पद्धत तुम्हाला हे कार्य पूर्ण होण्याची प्रतीक्षा करू देते .

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);
    }

सुरवातीपासून, आम्ही लक्षात घेतो की हुड अंतर्गत आम्ही ThreadPoolExecutor कन्स्ट्रक्टरला कॉल करत नाही. येथे आम्ही ForkJoinPool घटकासोबत काम करत आहोत. ThreadPoolExecutor प्रमाणे , हे AbstractExecutorService ची अंमलबजावणी आहे .

आमच्याकडे निवडण्यासाठी 2 पद्धती आहेत. प्रथम, आम्ही स्वतः सूचित करतो की आम्हाला समांतरतेची कोणती पातळी पहायची आहे. जर आम्ही हे मूल्य निर्दिष्ट केले नाही, तर आमच्या पूलची समांतरता Java व्हर्च्युअल मशीनसाठी उपलब्ध असलेल्या प्रोसेसर कोरच्या संख्येइतकी असेल.

हे सराव मध्ये कसे कार्य करते हे शोधणे बाकी आहे:

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 कार्ये तयार करतो जी त्यांची स्वतःची पूर्णता स्थिती प्रदर्शित करतात. त्यानंतर, आम्ही invokeAll पद्धत वापरून सर्व कार्ये सुरू करतो.

पूलमधील 10 थ्रेडवर 10 कार्ये अंमलात आणताना परिणाम:

ForkJoinPool-1-worker-10 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #9 ForkJoinPool-1-worker-5 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #4 ForkJoinPool-1-worker-8 थ्रेडवर प्रक्रिया केलेली वापरकर्ता विनंती #7
ForkJoinPool- वर प्रक्रिया केलेली वापरकर्ता विनंती #1 1-worker-2 थ्रेड
ForkJoinPool-1-worker-3 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #2 ForkJoinPool-1-worker-4 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #3 ForkJoinPool-1-worker-7 थ्रेडवर प्रक्रिया केलेली वापरकर्ता विनंती #6
प्रक्रिया केलेला वापरकर्ता ForkJoinPool-1-worker-1 थ्रेडवर विनंती #0 ForkJoinPool-1-worker-6 थ्रेडवर प्रक्रिया केलेली
वापरकर्ता विनंती #5 ForkJoinPool-1-worker-9 थ्रेडवर प्रक्रिया केलेली वापरकर्ता विनंती #8

आपण पाहतो की रांग तयार झाल्यानंतर, थ्रेड्स कार्यान्वित करण्यासाठी कार्ये घेतात. 10 थ्रेड्सच्या पूलमध्ये 20 कार्ये कशी वितरित केली जातील हे देखील तुम्ही तपासू शकता.

ForkJoinPool-1-worker-4 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #3 ForkJoinPool-1-worker-8 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #7 ForkJoinPool-1-worker-3 थ्रेडवर प्रक्रिया केलेली वापरकर्ता विनंती #2
ForkJoinPool- वर प्रक्रिया केलेली वापरकर्ता विनंती #4 1-worker-5 थ्रेड
ForkJoinPool-1-worker-2 थ्रेडवर प्रक्रिया
केलेली वापरकर्ता विनंती #1 ForkJoinPool-1-worker-6 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #5 ForkJoinPool-1-worker-9 थ्रेडवर प्रक्रिया केलेली वापरकर्ता विनंती #8
प्रक्रिया केलेला वापरकर्ता ForkJoinPool-1-worker-10 थ्रेडवर
विनंती #9 ForkJoinPool-1-worker-1 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #0 ForkJoinPool-1-worker-7 थ्रेडवर प्रक्रिया केलेली वापरकर्ता विनंती #6 ForkJoinPool-
1- वर प्रक्रिया केलेली वापरकर्ता विनंती #10 कामगार-9 धागा
ForkJoinPool-1-worker-1 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #12 ForkJoinPool-1-worker-8 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #13 ForkJoinPool-1-worker-6 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #11 ForkJoinPool- वर प्रक्रिया केलेली वापरकर्ता विनंती #15 1-worker-8 थ्रेड
ForkJoinPool-1-worker-1 थ्रेडवर प्रक्रिया केलेली वापरकर्ता विनंती #14
ForkJoinPool-1-worker-6 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #17 ForkJoinPool-1-worker-7 थ्रेडवर प्रक्रिया केलेली वापरकर्ता विनंती #16
प्रक्रिया केलेला वापरकर्ता 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 1-worker-13 थ्रेड
ForkJoinPool-1-worker-19 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #0 ForkJoinPool-1-worker-3 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #8 ForkJoinPool-1-worker-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 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #13 ForkJoinPool- वर प्रक्रिया केलेली वापरकर्ता विनंती #13 1-worker-13 थ्रेड
ForkJoinPool-1-worker-31 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #10 ForkJoinPool-1-worker-5 थ्रेडवर
प्रक्रिया केलेली वापरकर्ता विनंती #18 ForkJoinPool-1-worker-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;

invokeAll मेथडला कॉल केल्यानंतर या ऑब्जेक्टचे परीक्षण करण्यासाठी आम्ही Evaluate Expression क्रिया वापरू . हे करण्यासाठी, invokeAll पद्धतीनंतर, कोणतेही विधान जोडा, जसे की एम्प्टी सॉट, आणि त्यावर ब्रेकपॉइंट सेट करा.

आपण पाहू शकतो की पूलमध्ये 10 थ्रेड आहेत, परंतु वर्कर थ्रेड्सच्या अॅरेचा आकार 32 आहे. विचित्र, परंतु ठीक आहे. चला खोदत राहू. पूल तयार करताना, समांतरता पातळी 32 पेक्षा जास्त सेट करण्याचा प्रयत्न करूया, 40 म्हणा.

ExecutorService executorService = Executors.newWorkStealingPool(40);

डीबगर मध्ये, पाहूयाforkJoinPool ऑब्जेक्ट पुन्हा.

आता वर्कर थ्रेड्सच्या अॅरेचा आकार १२८ आहे. हे JVM च्या अंतर्गत ऑप्टिमायझेशनपैकी एक आहे असे आपण गृहीत धरू शकतो. चला JDK (openjdk-14) च्या कोडमध्ये शोधण्याचा प्रयत्न करूया:

जसे आम्हाला संशय आला: वर्कर थ्रेडच्या अॅरेचा आकार समांतर मूल्यावर बिटवाइज हाताळणी करून मोजला जातो. इथे नेमके काय चालले आहे हे जाणून घेण्याचा प्रयत्न करण्याची गरज नाही. असे ऑप्टिमायझेशन अस्तित्वात आहे हे फक्त जाणून घेणे पुरेसे आहे.

आमच्या उदाहरणाचा आणखी एक मनोरंजक पैलू म्हणजे invokeAll पद्धतीचा वापर . हे लक्षात घेण्यासारखे आहे की invokeAll पद्धत परिणाम किंवा त्याऐवजी परिणामांची सूची (आमच्या बाबतीत, एक List<Future<Void>>) देऊ शकते , जिथे आपण प्रत्येक कार्याचा परिणाम शोधू शकतो.

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

या विशेष प्रकारची सेवा आणि थ्रेड पूल अंदाजे, किंवा किमान अंतर्निहित, समरूपतेच्या पातळीसह कार्यांमध्ये वापरला जाऊ शकतो.