మన కోసం 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);
    }

ప్రారంభం నుండి, హుడ్ కింద మేము ThreadPoolExecutor కన్స్ట్రక్టర్‌ని పిలవడం లేదని మేము గమనించాము . ఇక్కడ మేము ForkJoinPool ఎంటిటీతో పని చేస్తున్నాము . ThreadPoolExecutor వలె , ఇది AbstractExecutorService యొక్క అమలు .

మేము ఎంచుకోవడానికి 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 టాస్క్‌లను సృష్టిస్తాము. ఆ తర్వాత, మేము invokeAll పద్ధతిని ఉపయోగించి అన్ని టాస్క్‌లను ప్రారంభిస్తాము .

పూల్‌లోని 10 థ్రెడ్‌లపై 10 టాస్క్‌లను అమలు చేసినప్పుడు ఫలితాలు:

ForkJoinPool-1-worker-10 థ్రెడ్‌లో వినియోగదారు అభ్యర్థన #9 ప్రాసెస్ చేయబడింది
ForkJoinPool-1-worker-5 థ్రెడ్‌లో
వినియోగదారు అభ్యర్థన #4 ప్రాసెస్ చేయబడింది ForkJoinPool-1-worker-8 థ్రెడ్‌లో
వినియోగదారు అభ్యర్థన #7 ప్రాసెస్ చేయబడింది ForkJoinPool- 1-వర్కర్-3 థ్రెడ్‌లో
1-వర్కర్-2 థ్రెడ్ ప్రాసెస్ చేయబడిన వినియోగదారు అభ్యర్థన
#2 ForkJoinPool-1-వర్కర్-4 థ్రెడ్‌లో వినియోగదారు
అభ్యర్థన #3 ప్రాసెస్ చేయబడింది
ForkJoinPool-1-worker-1 థ్రెడ్‌లో #0
అభ్యర్థన ప్రాసెస్ చేయబడిన వినియోగదారు అభ్యర్థన #5 ForkJoinPool-1-worker-6 థ్రెడ్‌లో
ప్రాసెస్ చేయబడిన వినియోగదారు అభ్యర్థన #8 ForkJoinPool-1-worker-9 థ్రెడ్‌లో ప్రాసెస్ చేయబడింది

క్యూ ఏర్పడిన తర్వాత, థ్రెడ్‌లు అమలు కోసం పనులను తీసుకుంటాయని మేము చూస్తాము. 10 థ్రెడ్‌ల పూల్‌లో 20 టాస్క్‌లు ఎలా పంపిణీ చేయబడతాయో కూడా మీరు తనిఖీ చేయవచ్చు.

ForkJoinPool-1-worker-4 థ్రెడ్‌లో వినియోగదారు అభ్యర్థన #3 ప్రాసెస్ చేయబడింది
ForkJoinPool-1-worker-8 థ్రెడ్‌లో
వినియోగదారు అభ్యర్థన #7 ప్రాసెస్ చేయబడింది ForkJoinPool-1-worker-3 థ్రెడ్‌లో
వినియోగదారు అభ్యర్థన #2 ప్రాసెస్ చేయబడింది
ForkJoinPool-1-worker-2 థ్రెడ్‌లో 1-worker-5 థ్రెడ్ ప్రాసెస్ చేయబడిన వినియోగదారు అభ్యర్థన #
1 ForkJoinPool-1-వర్కర్-6 థ్రెడ్‌లో వినియోగదారు
అభ్యర్థన #5 ప్రాసెస్ చేయబడింది.
ForkJoinPool-1-worker-10 థ్రెడ్‌లో అభ్యర్థన #9
ప్రాసెస్ చేయబడిన వినియోగదారు అభ్యర్థన #0 ForkJoinPool-1-వర్కర్-1 థ్రెడ్‌లో
ప్రాసెస్ చేయబడిన వినియోగదారు అభ్యర్థన #6 ForkJoinPool-1-worker-7 థ్రెడ్‌లో
ప్రాసెస్ చేయబడిన వినియోగదారు అభ్యర్థన #10 ForkJoinPool-1లో కార్మికుడు-9 థ్రెడ్
ForkJoinPool-1-worker-1 థ్రెడ్‌లో వినియోగదారు అభ్యర్థన #12 ప్రాసెస్ చేయబడింది
ForkJoinPool-1-worker-8 థ్రెడ్‌లో
వినియోగదారు అభ్యర్థన #13 ప్రాసెస్ చేయబడింది ForkJoinPool-1-worker-6 థ్రెడ్‌లో
వినియోగదారు అభ్యర్థన #11 ప్రాసెస్ చేయబడింది 1-వర్కర్-8 థ్రెడ్
ForkJoinPool-1-వర్కర్-1 థ్రెడ్ ప్రాసెస్ చేయబడిన వినియోగదారు అభ్యర్థన #14
ForkJoinPool-1-వర్కర్-6 థ్రెడ్‌లో ప్రాసెస్ చేయబడిన వినియోగదారు అభ్యర్థన #16
ForkJoinPool-1-వర్కర్-7 థ్రెడ్‌లో ప్రాసెస్ చేయబడిన వినియోగదారు అభ్యర్థన #16
ప్రాసెస్ చేయబడిన వినియోగదారు ForkJoinPool-1-worker-6 థ్రెడ్‌లో #19
అభ్యర్థన ప్రాసెస్ చేయబడిన వినియోగదారు అభ్యర్థన #18 ForkJoinPool-1-worker-1 థ్రెడ్‌లో

అవుట్‌పుట్ నుండి, కొన్ని థ్రెడ్‌లు అనేక టాస్క్‌లను పూర్తి చేయగలవని మనం చూడగలం ( 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-1-worker-19 థ్రెడ్‌లో 1-worker-13 థ్రెడ్ ప్రాసెస్ చేయబడిన వినియోగదారు అభ్యర్థన #0 ForkJoinPool
-1-వర్కర్-3 థ్రెడ్‌లో వినియోగదారు అభ్యర్థన #8 ప్రాసెస్
చేయబడింది.
ForkJoinPool-1-worker-17 థ్రెడ్‌లో #6
అభ్యర్థన ForkJoinPool-1-worker-9 థ్రెడ్‌లో వినియోగదారు అభ్యర్థన #3
ప్రాసెస్ చేయబడింది ForkJoinPool-1-worker-5 థ్రెడ్‌లో
వినియోగదారు అభ్యర్థన #1 ప్రాసెస్ చేయబడింది కార్మికుడు-23 థ్రెడ్
ForkJoinPool-1-worker-19 థ్రెడ్‌లో వినియోగదారు అభ్యర్థన #15 ప్రాసెస్ చేయబడింది
ForkJoinPool-1-worker-27 థ్రెడ్‌లో
వినియోగదారు అభ్యర్థన #14 ప్రాసెస్ చేయబడింది ForkJoinPool-1-worker-3 థ్రెడ్‌లో
వినియోగదారు అభ్యర్థన #11 ప్రాసెస్ చేయబడింది 1-వర్కర్-13 థ్రెడ్
ForkJoinPool-1-వర్కర్-31 థ్రెడ్‌లో వినియోగదారు అభ్యర్థన
#10
ప్రాసెస్
చేయబడింది ForkJoinPool-1-worker-21 థ్రెడ్‌లో #17 అభ్యర్థన
ప్రాసెస్ చేయబడిన వినియోగదారు అభ్యర్థన #19 ForkJoinPool-1-worker-17 థ్రెడ్‌లో

ఈ అవుట్‌పుట్‌లో, మేము పూల్‌లోని థ్రెడ్‌లను "అడిగాము" అని గుర్తించదగినది. ఇంకా ఏమిటంటే, వర్కర్ థ్రెడ్‌ల పేర్లు ఒకటి నుండి పదికి వెళ్లవు, బదులుగా కొన్నిసార్లు పది కంటే ఎక్కువగా ఉంటాయి. ప్రత్యేకమైన పేర్లను పరిశీలిస్తే, నిజంగా పది మంది కార్మికులు (3, 5, 9, 13, 17, 19, 21, 23, 27 మరియు 31) ఉన్నారని మేము చూస్తాము. ఇక్కడ ఇది ఎందుకు జరిగింది అని అడగడం చాలా సహేతుకమైనది? మీకు ఏమి జరుగుతుందో అర్థం కానప్పుడు, డీబగ్గర్‌ని ఉపయోగించండి.

ఇది మేము చేస్తాము. తారాగణం చేద్దాంకార్యనిర్వాహక సేవForkJoinPool కి ఆబ్జెక్ట్ :

final ForkJoinPool forkJoinPool = (ForkJoinPool) executorService;

InvokeAll పద్ధతికి కాల్ చేసిన తర్వాత ఈ వస్తువును పరిశీలించడానికి మేము మూల్యాంకనం వ్యక్తీకరణ చర్యను ఉపయోగిస్తాము . దీన్ని చేయడానికి, invokeAll పద్ధతి తర్వాత, ఖాళీ సౌట్ వంటి ఏదైనా స్టేట్‌మెంట్‌ని జోడించి, దానిపై బ్రేక్‌పాయింట్‌ను సెట్ చేయండి.

పూల్‌లో 10 థ్రెడ్‌లు ఉన్నాయని మనం చూడవచ్చు, కానీ వర్కర్ థ్రెడ్‌ల శ్రేణి పరిమాణం 32. విచిత్రమైనది, అయితే సరే. తవ్వుతూనే ఉంటాం. పూల్‌ను సృష్టించేటప్పుడు, సమాంతరత స్థాయిని 32 కంటే ఎక్కువ సెట్ చేయడానికి ప్రయత్నిద్దాం, 40 అని చెప్పండి.

ExecutorService executorService = Executors.newWorkStealingPool(40);

డీబగ్గర్‌లో, చూద్దాంforkJoinPool ఆబ్జెక్ట్ మళ్లీ.

ఇప్పుడు వర్కర్ థ్రెడ్‌ల శ్రేణి పరిమాణం 128. ఇది JVM యొక్క అంతర్గత ఆప్టిమైజేషన్‌లలో ఒకటి అని మనం భావించవచ్చు. దానిని JDK (openjdk-14) కోడ్‌లో కనుగొనడానికి ప్రయత్నిద్దాం:

మేము అనుమానించినట్లే: వర్కర్ థ్రెడ్‌ల శ్రేణి పరిమాణం సమాంతర విలువపై బిట్‌వైస్ మానిప్యులేషన్‌లను చేయడం ద్వారా లెక్కించబడుతుంది. ఇక్కడ సరిగ్గా ఏమి జరుగుతుందో తెలుసుకోవడానికి మనం ప్రయత్నించాల్సిన అవసరం లేదు. అటువంటి ఆప్టిమైజేషన్ ఉందని తెలుసుకోవడం సరిపోతుంది.

మా ఉదాహరణలో మరొక ఆసక్తికరమైన అంశం invokeAll పద్ధతిని ఉపయోగించడం . InvokeAll పద్ధతి ఫలితాన్ని లేదా ఫలితాల జాబితాను అందించగలదని గమనించాలి (మా విషయంలో, జాబితా<భవిష్యత్<శూన్యత>>) , ఇక్కడ మేము ప్రతి పని యొక్క ఫలితాన్ని కనుగొనవచ్చు.

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

ఈ ప్రత్యేక రకమైన సేవ మరియు థ్రెడ్ పూల్‌ని ఊహాజనిత లేదా కనీసం అవ్యక్తమైన, సమ్మతి స్థాయితో టాస్క్‌లలో ఉపయోగించవచ్చు.