పరిచయం
కాబట్టి, జావాలో థ్రెడ్లు ఉన్నాయని మనకు తెలుసు. మీరు బెటర్ టుగెదర్: జావా మరియు థ్రెడ్ క్లాస్ అనే రివ్యూలో దాని గురించి చదువుకోవచ్చు . పార్ట్ I - అమలు యొక్క థ్రెడ్లు .
సాధారణ కోడ్ను మరొకసారి చూద్దాం:
public static void main(String[] args) throws Exception {
Runnable task = () -> {
System.out.println("Task executed");
};
Thread thread = new Thread(task);
thread.start();
}
మీరు చూడగలిగినట్లుగా, టాస్క్ను ప్రారంభించడానికి కోడ్ చాలా విలక్షణమైనది, కానీ కొత్త పని కోసం మేము దానిని పునరావృతం చేయాలి. ఒక ప్రత్యేక పద్ధతిలో ఉంచడం ఒక పరిష్కారం, ఉదా execute(Runnable runnable). కానీ జావా సృష్టికర్తలు మా దుస్థితిని పరిగణనలోకి తీసుకుని ఇంటర్ఫేస్తో ముందుకు వచ్చారు Executor:
public static void main(String[] args) throws Exception {
Runnable task = () -> System.out.println("Task executed");
Executor executor = (runnable) -> {
new Thread(runnable).start();
};
executor.execute(task);
}
Runnableఈ కోడ్ స్పష్టంగా మరింత సంక్షిప్తంగా ఉంది: ఇప్పుడు మనం థ్రెడ్లో ప్రారంభించడానికి కోడ్ని వ్రాస్తాము . అది చాలా బాగుంది, కాదా? కానీ ఇది ప్రారంభం మాత్రమే:
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html
Executorఇంటర్ఫేస్కు ఉప ఇంటర్ఫేస్ ఉంది ExecutorService. ఈ ఇంటర్ఫేస్కు సంబంధించిన జావాడోక్, షట్ డౌన్ చేయడానికి పద్ధతులను అందించే ExecutorServiceప్రత్యేకతను వివరిస్తుందని చెప్పారు . ఇది ఎగ్జిక్యూషన్ ప్రాసెస్ను ట్రాక్ చేయడానికి ఒక పొందడం కూడా సాధ్యం చేస్తుంది . గతంలో, బెటర్ కలిసి: జావా మరియు థ్రెడ్ క్లాస్. పార్ట్ IV — కాల్ చేయదగినది, భవిష్యత్తు మరియు స్నేహితులు , మేము క్లుప్తంగా సామర్థ్యాలను సమీక్షించాము . మీరు దానిని మరచిపోయినా లేదా ఎప్పటికీ చదవకపోయినా, మీ జ్ఞాపకశక్తిని రిఫ్రెష్ చేయమని నేను సూచిస్తున్నాను;) జావాడోక్ ఇంకా ఏమి చెబుతుంది? యొక్క డిఫాల్ట్ ఇంప్లిమెంటేషన్లను రూపొందించడానికి మాకు ప్రత్యేక ఫ్యాక్టరీ ఉందని ఇది మాకు చెబుతుంది . ExecutorExecutorjava.util.concurrent.FutureFuturejava.util.concurrent.ExecutorsExecutorService
ఎగ్జిక్యూటర్ సర్వీస్
సమీక్షిద్దాం. మేము థ్రెడ్లో ఒక నిర్దిష్ట పనినిExecutorఅమలు చేయాలి (అంటే కాల్ చేయడానికి ) మరియు థ్రెడ్ను సృష్టించే కోడ్ మన నుండి దాచబడుతుంది. execute()మేము కలిగి — పురోగతిని నియంత్రించడానికి అనేక ఎంపికలను కలిగి ఉన్న ExecutorServiceనిర్దిష్టమైనది . Executorమరియు Executorsమేము ఒక సృష్టించడానికి అనుమతించే ఫ్యాక్టరీని కలిగి ఉన్నాము ExecutorService. ఇప్పుడు మనమే చేద్దాం:
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> task = () -> Thread.currentThread().getName();
ExecutorService service = Executors.newFixedThreadPool(2);
for (int i = 0; i < 5; i++) {
Future result = service.submit(task);
System.out.println(result.get());
}
service.shutdown();
}
మేము ఫిక్స్డ్ థ్రెడ్ పూల్ను పేర్కొన్నట్లు మీరు చూడవచ్చు, దీని పరిమాణం 2. ఆ తర్వాత మేము టాస్క్లను ఒక్కొక్కటిగా పూల్కి సమర్పిస్తాము. ప్రతి పని Stringథ్రెడ్ పేరు ( currentThread().GetName())ను కలిగి ఉంటుంది. చివరిలో మూసివేయడం చాలా ముఖ్యం ExecutorService, లేకపోతే మా ప్రోగ్రామ్ ముగియదు. ఫ్యాక్టరీ Executorsఅదనపు ఫ్యాక్టరీ పద్ధతులను కలిగి ఉంది. newSingleThreadExecutorఉదాహరణకు, మేము కేవలం ఒక థ్రెడ్ ( )తో కూడిన పూల్ని లేదా కాష్ ( )ని కలిగి ఉన్న పూల్ని సృష్టించవచ్చు, newCachedThreadPoolదాని నుండి థ్రెడ్లు 1 నిమిషం పాటు నిష్క్రియంగా ఉన్న తర్వాత తీసివేయబడతాయి. వాస్తవానికి, వీటిని నిరోధించే క్యూExecutorService ద్వారా మద్దతు ఇవ్వబడుతుంది , దీనిలో టాస్క్లు ఉంచబడతాయి మరియు ఏ టాస్క్లు అమలు చేయబడతాయి. క్యూలను నిరోధించడం గురించి మరింత సమాచారం ఈ వీడియోలో చూడవచ్చు . మీరు దీన్ని కూడా చదవగలరుBlockingQueue గురించి సమీక్షించండి . మరియు "ArayBlockingQueue కంటే LinkedBlockingQueueని ఎప్పుడు ఇష్టపడాలి?" అనే ప్రశ్నకు సమాధానాన్ని తనిఖీ చేయండి. సరళమైన పదాలలో, ఒక BlockingQueueథ్రెడ్ను రెండు సందర్భాలలో బ్లాక్ చేస్తుంది:
- థ్రెడ్ ఖాళీ క్యూ నుండి వస్తువులను పొందడానికి ప్రయత్నిస్తుంది
- థ్రెడ్ అంశాలను పూర్తి క్యూలో ఉంచడానికి ప్రయత్నిస్తుంది
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
లేదా
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
మనం చూడగలిగినట్లుగా, ExecutorServiceకర్మాగార పద్ధతుల్లో అమలులు సృష్టించబడతాయి. మరియు చాలా వరకు, మేము గురించి మాట్లాడుతున్నాము ThreadPoolExecutor. పనిని ప్రభావితం చేసే పారామితులు మాత్రమే మార్చబడతాయి.
https://en.wikipedia.org/wiki/Thread_pool#/media/File:Thread_pool.svg
ThreadPoolExecutor
మేము ఇంతకు ముందు చూసినట్లుగా,ThreadPoolExecutorసాధారణంగా ఫ్యాక్టరీ పద్ధతులలో సృష్టించబడుతుంది. థ్రెడ్ల గరిష్ట మరియు కనిష్ట సంఖ్య, అలాగే ఏ రకమైన క్యూ ఉపయోగించబడుతుందో మనం పాస్ చేసే ఆర్గ్యుమెంట్ల ద్వారా కార్యాచరణ ప్రభావితమవుతుంది. కానీ java.util.concurrent.BlockingQueueఇంటర్ఫేస్ యొక్క ఏదైనా అమలును ఉపయోగించవచ్చు. గురించి మాట్లాడుతూ ThreadPoolExecutor, మేము కొన్ని ఆసక్తికరమైన లక్షణాలను పేర్కొనాలి. ThreadPoolExecutorఉదాహరణకు, అందుబాటులో ఖాళీ లేనట్లయితే మీరు టాస్క్లను aకి సమర్పించలేరు :
public static void main(String[] args) throws ExecutionException, InterruptedException {
int threadBound = 2;
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, threadBound,
0L, TimeUnit.SECONDS, new SynchronousQueue<>());
Callable<String> task = () -> {
Thread.sleep(1000);
return Thread.currentThread().getName();
};
for (int i = 0; i < threadBound + 1; i++) {
threadPoolExecutor.submit(task);
}
threadPoolExecutor.shutdown();
}
ఈ కోడ్ ఇలాంటి లోపంతో క్రాష్ అవుతుంది:
Task java.util.concurrent.FutureTask@7cca494b rejected from java.util.concurrent.ThreadPoolExecutor@7ba4f24f[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]
మరో మాటలో చెప్పాలంటే, taskసమర్పించడం సాధ్యం కాదు, ఎందుకంటే SynchronousQueueఇది వాస్తవానికి ఒకే మూలకాన్ని కలిగి ఉండేలా రూపొందించబడింది మరియు దానిలో మరేదైనా ఉంచడానికి మమ్మల్ని అనుమతించదు. queued tasksఇక్కడ మనకు సున్నా ("క్యూడ్ టాస్క్లు = 0") ఉన్నట్లు మనం చూడవచ్చు . కానీ దీని గురించి వింత ఏమీ లేదు, ఎందుకంటే ఇది ఒక ప్రత్యేక లక్షణం SynchronousQueue, వాస్తవానికి ఇది ఎల్లప్పుడూ ఖాళీగా ఉండే 1-మూలకం క్యూ! ఒక థ్రెడ్ ఒక మూలకాన్ని క్యూలో ఉంచినప్పుడు, మరొక థ్రెడ్ క్యూ నుండి మూలకాన్ని తీసుకునే వరకు వేచి ఉంటుంది. దీని ప్రకారం, మేము దానిని భర్తీ చేయవచ్చు new LinkedBlockingQueue<>(1)మరియు లోపం ఇప్పుడు చూపుకి మార్చబడుతుంది queued tasks = 1. క్యూలో 1 మూలకం మాత్రమే ఉన్నందున, మేము రెండవ మూలకాన్ని జోడించలేము. మరియు అది ప్రోగ్రామ్ విఫలం కావడానికి కారణమవుతుంది. క్యూ గురించి మా చర్చను కొనసాగిస్తూ, ఇది గమనించదగ్గ విషయంThreadPoolExecutorక్యూలో సర్వీసింగ్ కోసం తరగతికి అదనపు పద్ధతులు ఉన్నాయి. ఉదాహరణకు, threadPoolExecutor.purge()క్యూలో స్థలాన్ని ఖాళీ చేయడానికి పద్ధతి రద్దు చేయబడిన అన్ని టాస్క్లను క్యూ నుండి తీసివేస్తుంది. మరొక ఆసక్తికరమైన క్యూ-సంబంధిత ఫంక్షన్ తిరస్కరించబడిన పనుల కోసం హ్యాండ్లర్:
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.SECONDS, new SynchronousQueue());
Callable<String> task = () -> Thread.currentThread().getName();
threadPoolExecutor.setRejectedExecutionHandler((runnable, executor) -> System.out.println("Rejected"));
for (int i = 0; i < 5; i++) {
threadPoolExecutor.submit(task);
}
threadPoolExecutor.shutdown();
}
ఈ ఉదాహరణలో, మా హ్యాండ్లర్ Rejectedక్యూలో ఉన్న టాస్క్ని తిరస్కరించిన ప్రతిసారీ ప్రదర్శిస్తుంది. అనుకూలమైనది, కాదా? అదనంగా, ThreadPoolExecutorఒక ఆసక్తికరమైన ఉపవర్గం ఉంది: ScheduledThreadPoolExecutor, ఇది ఒక ScheduledExecutorService. ఇది టైమర్ ఆధారంగా పనిని చేయగల సామర్థ్యాన్ని అందిస్తుంది.
షెడ్యూల్డ్ ఎగ్జిక్యూటర్ సర్వీస్
ScheduledExecutorService(ఇది ఒక రకం ExecutorService) షెడ్యూల్లో టాస్క్లను అమలు చేయడానికి అనుమతిస్తుంది. ఒక ఉదాహరణ చూద్దాం:
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4);
Callable<String> task = () -> {
System.out.println(Thread.currentThread().getName());
return Thread.currentThread().getName();
};
scheduledExecutorService.schedule(task, 1, TimeUnit.MINUTES);
scheduledExecutorService.shutdown();
}
ఇక్కడ ప్రతిదీ సులభం. టాస్క్లు సమర్పించబడతాయి మరియు మేము ఒక పొందుతాము java.util.concurrent.ScheduledFuture. కింది పరిస్థితులలో కూడా ఒక షెడ్యూల్ సహాయపడవచ్చు:
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4);
Runnable task = () -> {
System.out.println(Thread.currentThread().getName());
};
scheduledExecutorService.scheduleAtFixedRate(task, 1, 2, TimeUnit.SECONDS);
ఇక్కడ మేము Runnableనిర్ణీత పౌనఃపున్యం ("ఫిక్స్డ్రేట్")లో నిర్దిష్ట ప్రారంభ ఆలస్యంతో అమలు కోసం ఒక విధిని సమర్పించాము. ఈ సందర్భంలో, 1 సెకను తర్వాత, ప్రతి 2 సెకన్లకు పనిని అమలు చేయడం ప్రారంభమవుతుంది. ఇదే విధమైన ఎంపిక ఉంది:
scheduledExecutorService.scheduleWithFixedDelay(task, 1, 2, TimeUnit.SECONDS);
కానీ ఈ సందర్భంలో, ప్రతి అమలుకు మధ్య నిర్దిష్ట విరామంతో పనులు నిర్వహించబడతాయి. అంటే, ఇది task1 సెకను తర్వాత అమలు చేయబడుతుంది. అప్పుడు, అది పూర్తయిన వెంటనే, 2 సెకన్లు గడిచిపోతాయి, ఆపై కొత్త పని ప్రారంభించబడుతుంది. ఈ అంశంపై కొన్ని అదనపు వనరులు ఇక్కడ ఉన్నాయి:
- జావాలో థ్రెడ్ పూల్స్కు పరిచయం
- జావాలో థ్రెడ్ పూల్స్ పరిచయం
- జావా మల్టీథ్రెడింగ్ స్టీపుల్చేజ్: ఎగ్జిక్యూటర్లలో విధులను రద్దు చేయడం
- బ్యాక్గ్రౌండ్ టాస్క్ల కోసం జావా ఎగ్జిక్యూటర్లను ఉపయోగించడం
https://dzone.com/articles/diving-into-java-8s-newworkstealingpools
వర్క్ స్టీలింగ్ పూల్
పై థ్రెడ్ పూల్స్తో పాటు, మరొకటి ఉంది. కాస్త స్పెషల్ అని నిజాయితీగా చెప్పొచ్చు. దీనిని పని దొంగిలించే కొలను అంటారు. సంక్షిప్తంగా, పని దొంగతనం అనేది ఒక అల్గారిథమ్, దీనిలో నిష్క్రియ థ్రెడ్లు ఇతర థ్రెడ్ల నుండి టాస్క్లను లేదా షేర్డ్ క్యూ నుండి టాస్క్లను తీసుకోవడం ప్రారంభిస్తాయి. ఒక ఉదాహరణ చూద్దాం:
public static void main(String[] args) {
Object lock = new Object();
ExecutorService executorService = Executors.newCachedThreadPool();
Callable<String> task = () -> {
System.out.println(Thread.currentThread().getName());
lock.wait(2000);
System.out.println("Finished");
return "result";
};
for (int i = 0; i < 5; i++) {
executorService.submit(task);
}
executorService.shutdown();
}
మేము ఈ కోడ్ని అమలు చేస్తే, అది ExecutorServiceమన కోసం 5 థ్రెడ్లను సృష్టిస్తుంది, ఎందుకంటే ప్రతి థ్రెడ్ లాక్ ఆబ్జెక్ట్ కోసం వెయిట్ క్యూలో ఉంచబడుతుంది. మేము ఇప్పటికే బెటర్లో మానిటర్లు మరియు లాక్లను కలిసి కనుగొన్నాము : జావా మరియు థ్రెడ్ క్లాస్. పార్ట్ II - సమకాలీకరణ . Executors.newCachedThreadPool()ఇప్పుడు తో భర్తీ చేద్దాం Executors.newWorkStealingPool(). ఏమి మారుతుంది? మా టాస్క్లు 5 కంటే తక్కువ థ్రెడ్లలో అమలు చేయబడడాన్ని మేము చూస్తాము. CachedThreadPoolప్రతి పనికి ఒక థ్రెడ్ని సృష్టిస్తుందని గుర్తుంచుకోవాలా ? థ్రెడ్ బ్లాక్ చేయబడినందున wait(), తదుపరి పనులు పూర్తి కావాలి మరియు పూల్లో వాటి కోసం కొత్త థ్రెడ్లు సృష్టించబడ్డాయి. దొంగతనం చేసే కొలనుతో, థ్రెడ్లు ఎప్పటికీ నిష్క్రియంగా ఉండవు. వారు తమ పొరుగువారి పనులను చేయడం ప్రారంభిస్తారు. ఇతర థ్రెడ్ పూల్ల నుండి ఏమి WorkStealingPoolభిన్నంగా ఉంటుంది? మాయా వాస్తవంForkJoinPoolదాని లోపల నివసిస్తుంది:
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
నిజానికి, ఇంకొక తేడా ఉంది. డిఫాల్ట్గా, a కోసం సృష్టించబడిన థ్రెడ్లు ForkJoinPoolసాధారణ థ్రెడ్ల వలె కాకుండా డెమోన్ థ్రెడ్లు ThreadPool. సాధారణంగా, మీరు డెమోన్ థ్రెడ్లను గుర్తుంచుకోవాలి, ఎందుకంటే, ఉదాహరణకు, డెమోన్ కాని థ్రెడ్లను సృష్టించే CompletableFutureమీ స్వంతంగా పేర్కొనకపోతే డెమోన్ థ్రెడ్లను కూడా ఉపయోగిస్తుంది . ThreadFactoryఊహించని చోట్ల దాగి ఉండే ఆశ్చర్యకరమైనవి ఇవే! :)
ForkJoinPool
ForkJoinPoolఈ భాగంలో, మనం "అండర్ ది హుడ్" గురించి (ఫోర్క్/జాయిన్ ఫ్రేమ్వర్క్ అని కూడా పిలుస్తారు) గురించి మళ్లీ మాట్లాడతాము WorkStealingPool. సాధారణంగా, ఫోర్క్/జాయిన్ ఫ్రేమ్వర్క్ జావా 1.7లో తిరిగి కనిపించింది. మరియు జావా 11 చేతికి దగ్గరగా ఉన్నప్పటికీ, ఇది ఇప్పటికీ గుర్తుంచుకోవలసిన విషయం. ఇది అత్యంత సాధారణ అమలు కాదు, కానీ ఇది చాలా ఆసక్తికరంగా ఉంటుంది. వెబ్లో దీని గురించి మంచి సమీక్ష ఉంది: జావా ఫోర్క్-జాయిన్ ఫ్రేమ్వర్క్ను ఉదాహరణలతో అర్థం చేసుకోవడం . ForkJoinPoolమీద ఆధారపడుతుంది java.util.concurrent.RecursiveTask. కూడా ఉంది java.util.concurrent.RecursiveAction. RecursiveActionఫలితాన్ని తిరిగి ఇవ్వదు. అందువలన, RecursiveTaskపోలి ఉంటుంది Callableమరియు RecursiveActionపోలి ఉంటుంది unnable. పేరులో రెండు ముఖ్యమైన పద్ధతుల పేర్లు ఉన్నాయని మనం చూడవచ్చు: forkమరియు join. దిforkపద్ధతి ఒక ప్రత్యేక థ్రెడ్లో అసమకాలికంగా కొంత పనిని ప్రారంభిస్తుంది. మరియు joinపని పూర్తయ్యే వరకు వేచి ఉండటానికి పద్ధతి మిమ్మల్ని అనుమతిస్తుంది. ఉత్తమ అవగాహన పొందడానికి, మీరు జావా 8లో ఇంపెరేటివ్ ప్రోగ్రామింగ్ నుండి ఫోర్క్/జాయిన్ టు పారలల్ స్ట్రీమ్లను చదవాలి .
సారాంశం
బాగా, ఇది సమీక్ష యొక్క ఈ భాగాన్ని మూసివేస్తుంది.Executorథ్రెడ్లను అమలు చేయడానికి ఇది మొదట కనుగొనబడిందని మేము తెలుసుకున్నాము . అప్పుడు జావా సృష్టికర్తలు ఆలోచనను కొనసాగించాలని నిర్ణయించుకున్నారు మరియు ExecutorService. మరియు ExecutorServiceఉపయోగించి అమలు కోసం టాస్క్లను సమర్పించడానికి మరియు సేవను మూసివేస్తాము. ఇంప్లిమెంటేషన్లు కావాలి కాబట్టి , వారు ఫ్యాక్టరీ పద్ధతులతో ఒక తరగతిని వ్రాసారు మరియు దానిని పిలిచారు . ఇది థ్రెడ్ పూల్స్ ( ) సృష్టించడానికి మిమ్మల్ని అనుమతిస్తుంది . అదనంగా, ఎగ్జిక్యూషన్ షెడ్యూల్ను పేర్కొనడానికి మాకు అనుమతించే థ్రెడ్ పూల్స్ కూడా ఉన్నాయి. మరియు ఒక వెనుక దాక్కుంటుంది . నేను పైన వ్రాసినది మీకు ఆసక్తికరంగా మాత్రమే కాకుండా, అర్థమయ్యేలా కూడా ఉందని నేను ఆశిస్తున్నాను :) మీ సూచనలు మరియు వ్యాఖ్యలను వినడానికి నేను ఎల్లప్పుడూ సంతోషిస్తున్నాను. submit()invoke()ExecutorServiceExecutorsThreadPoolExecutorForkJoinPoolWorkStealingPoolకలిసి ఉత్తమం: జావా మరియు థ్రెడ్ క్లాస్. పార్ట్ I — థ్రెడ్స్ ఆఫ్ ఎగ్జిక్యూషన్ కలిసి మెరుగ్గా ఉంటుంది: జావా మరియు థ్రెడ్ క్లాస్. పార్ట్ II — సమకాలీకరణ ఉత్తమం: జావా మరియు థ్రెడ్ క్లాస్. పార్ట్ III — కలిసి మెరుగ్గా పరస్పర చర్య: జావా మరియు థ్రెడ్ క్లాస్. పార్ట్ IV — కాల్ చేయదగినది, భవిష్యత్తు మరియు స్నేహితులు కలిసి ఉండటం మంచిది: జావా మరియు థ్రెడ్ క్లాస్. పార్ట్ VI — ఫైర్ అవే!
GO TO FULL VERSION