హాయ్! మేము మల్టీథ్రెడింగ్ గురించి మా అధ్యయనాన్ని కొనసాగిస్తాము.
పిలిచినప్పుడు , అది వాస్తవానికి ఇతర థ్రెడ్లతో మాట్లాడుతుంది: 'హే, అబ్బాయిలు.
volatile
ఈ రోజు మనం కీవర్డ్ మరియు పద్ధతి గురించి తెలుసుకుందాం yield()
. ప్రవేశిద్దాం :)
అస్థిర కీవర్డ్
మల్టీథ్రెడ్ అప్లికేషన్లను క్రియేట్ చేస్తున్నప్పుడు, మనం రెండు తీవ్రమైన సమస్యలను ఎదుర్కోవచ్చు. ముందుగా, మల్టీథ్రెడ్ అప్లికేషన్ రన్ అవుతున్నప్పుడు, వివిధ థ్రెడ్లు వేరియబుల్స్ విలువలను కాష్ చేయగలవు (దీని గురించి మనం ఇప్పటికే 'అస్థిరతను ఉపయోగించడం' అనే పాఠంలో మాట్లాడాము ). ఒక థ్రెడ్ వేరియబుల్ విలువను మార్చే పరిస్థితిని మీరు కలిగి ఉండవచ్చు , కానీ రెండవ థ్రెడ్ మార్పును చూడదు, ఎందుకంటే ఇది వేరియబుల్ యొక్క కాష్ చేసిన కాపీతో పని చేస్తుంది. సహజంగానే, పరిణామాలు తీవ్రంగా ఉండవచ్చు. ఇది ఏదైనా పాత వేరియబుల్ మాత్రమే కాకుండా మీ బ్యాంక్ ఖాతా బ్యాలెన్స్ అని అనుకుందాం, ఇది అకస్మాత్తుగా యాదృచ్ఛికంగా పైకి క్రిందికి దూకడం ప్రారంభిస్తుంది :) అది సరదాగా అనిపించడం లేదు, సరియైనదా? రెండవది, జావాలో, అన్ని ఆదిమ రకాలను చదవడానికి మరియు వ్రాయడానికి కార్యకలాపాలు,long
double
, పరమాణువులు. సరే, ఉదాహరణకు, మీరు ఒక థ్రెడ్పై వేరియబుల్ విలువను మార్చినట్లయితే int
మరియు మరొక థ్రెడ్లో మీరు వేరియబుల్ విలువను చదివితే, మీరు దాని పాత విలువను లేదా కొత్తదాన్ని పొందుతారు, అంటే మార్పు ఫలితంగా వచ్చిన విలువ. థ్రెడ్ 1లో. 'ఇంటర్మీడియట్ విలువలు' లేవు. long
అయితే, ఇది s మరియు s తో పని చేయదు double
. ఎందుకు? క్రాస్-ప్లాట్ఫారమ్ మద్దతు కారణంగా. జావా యొక్క మార్గదర్శక సూత్రం 'ఒకసారి వ్రాయండి, ఎక్కడికైనా పరిగెత్తండి' అని మేము చెప్పినట్లు ప్రారంభ స్థాయిలలో గుర్తుందా? అంటే క్రాస్-ప్లాట్ఫారమ్ మద్దతు. మరో మాటలో చెప్పాలంటే, జావా అప్లికేషన్ అన్ని రకాల విభిన్న ప్లాట్ఫారమ్లపై నడుస్తుంది. ఉదాహరణకు, Windows ఆపరేటింగ్ సిస్టమ్లలో, Linux లేదా MacOS యొక్క విభిన్న సంస్కరణలు. వాటన్నింటిపై ఎలాంటి ఇబ్బంది లేకుండా నడుస్తుంది. 64 బిట్స్లో బరువు,long
double
జావాలో 'భారీ' ఆదిమానవులు. మరియు నిర్దిష్ట 32-బిట్ ప్లాట్ఫారమ్లు 64-బిట్ వేరియబుల్స్ యొక్క అటామిక్ రీడింగ్ మరియు రైటింగ్ను అమలు చేయవు. ఇటువంటి వేరియబుల్స్ రెండు ఆపరేషన్లలో చదవబడతాయి మరియు వ్రాయబడతాయి. మొదట, మొదటి 32 బిట్లు వేరియబుల్కు వ్రాయబడతాయి, ఆపై మరో 32 బిట్లు వ్రాయబడతాయి. ఫలితంగా, ఒక సమస్య తలెత్తవచ్చు. ఒక థ్రెడ్ కొంత 64-బిట్ విలువను X
వేరియబుల్కు వ్రాసి రెండు ఆపరేషన్లలో చేస్తుంది. అదే సమయంలో, రెండవ థ్రెడ్ వేరియబుల్ విలువను చదవడానికి ప్రయత్నిస్తుంది మరియు ఆ రెండు ఆపరేషన్ల మధ్య అలా చేస్తుంది - మొదటి 32 బిట్లు వ్రాయబడినప్పుడు, కానీ రెండవ 32 బిట్లు వ్రాయబడలేదు. ఫలితంగా, ఇది ఇంటర్మీడియట్, తప్పు విలువను చదువుతుంది మరియు మాకు బగ్ ఉంది. ఉదాహరణకు, అటువంటి ప్లాట్ఫారమ్లో మనం నంబర్ను 9223372036854775809 కి వ్రాయడానికి ప్రయత్నిస్తాము. వేరియబుల్కి, అది 64 బిట్లను ఆక్రమిస్తుంది. బైనరీ రూపంలో, ఇది ఇలా కనిపిస్తుంది: 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001 మొదటి థ్రెడ్ సంఖ్యను వేరియబుల్కు వ్రాయడం ప్రారంభిస్తుంది. మొదట, ఇది మొదటి 32 బిట్లు ( 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ) వ్రాస్తుంది. మరియు వేరియబుల్ యొక్క ఇంటర్మీడియట్ విలువను (10000000000000000000000000000000000000) చదవడం ద్వారా ఈ ఆపరేషన్ల మధ్య రెండవ థ్రెడ్ వెడ్జ్ చేయబడవచ్చు, ఇది ఇప్పటికే వ్రాయబడిన మొదటి 32 బిట్లు. దశాంశ వ్యవస్థలో, ఈ సంఖ్య 2,147,483,648. మరో మాటలో చెప్పాలంటే, మేము 9223372036854775809 సంఖ్యను వేరియబుల్కు వ్రాయాలనుకుంటున్నాము, అయితే ఈ ఆపరేషన్ కొన్ని ప్లాట్ఫారమ్లలో పరమాణువు కానందున, మనకు 2,147,483,648 అనే చెడు సంఖ్య ఉంది, ఇది ఎక్కడా బయటకు వచ్చి తెలియని ప్రభావాన్ని చూపుతుంది కార్యక్రమం. రెండవ థ్రెడ్ వ్రాయడం పూర్తయ్యేలోపు వేరియబుల్ యొక్క విలువను చదవండి, అనగా థ్రెడ్ మొదటి 32 బిట్లను చూసింది, కానీ రెండవ 32 బిట్లను కాదు. అయితే ఈ సమస్యలు నిన్న మొన్న వచ్చినవి కావు. జావా వాటిని ఒకే కీవర్డ్తో పరిష్కరిస్తుంది: volatile
. మేము ఉపయోగిస్తేvolatile
మా ప్రోగ్రామ్లో కొన్ని వేరియబుల్ని ప్రకటించేటప్పుడు కీవర్డ్…
public class Main {
public volatile long x = 2222222222222222222L;
public static void main(String[] args) {
}
}
…దాని అర్థం ఏమిటంటే:
- ఇది ఎప్పుడూ అణువణువూ చదవబడుతుంది మరియు వ్రాయబడుతుంది. అది 64-బిట్
double
లేదాlong
. - జావా మెషీన్ దానిని కాష్ చేయదు. కాబట్టి 10 థ్రెడ్లు వాటి స్వంత స్థానిక కాపీలతో పని చేసే పరిస్థితి మీకు ఉండదు.
దిగుబడి () పద్ధతి
మేము ఇప్పటికే అనేకThread
తరగతి పద్ధతులను సమీక్షించాము, కానీ మీకు కొత్తగా ఉండే ముఖ్యమైనది ఒకటి ఉంది. అది yield()
పద్ధతి . మరియు అది దాని పేరు సూచించినట్లు ఖచ్చితంగా చేస్తుంది! మేము థ్రెడ్లో పద్ధతిని 
yield
నేను ఎక్కడికీ వెళ్లడానికి ప్రత్యేకంగా ఆతురుతలో లేను, కాబట్టి మీలో ఎవరికైనా ప్రాసెసర్ సమయాన్ని పొందడం ముఖ్యం అయితే, తీసుకోండి — నేను వేచి ఉండగలను'. ఇది ఎలా పని చేస్తుందో ఇక్కడ ఒక సాధారణ ఉదాహరణ:
public class ThreadExample extends Thread {
public ThreadExample() {
this.start();
}
public void run() {
System.out.println(Thread.currentThread().getName() + " yields its place to others");
Thread.yield();
System.out.println(Thread.currentThread().getName() + " has finished executing.");
}
public static void main(String[] args) {
new ThreadExample();
new ThreadExample();
new ThreadExample();
}
}
మేము వరుసగా మూడు థ్రెడ్లను సృష్టించి, ప్రారంభిస్తాము: Thread-0
, Thread-1
, మరియు Thread-2
. Thread-0
మొదట మొదలవుతుంది మరియు వెంటనే ఇతరులకు ఇస్తుంది. తర్వాత Thread-1
ప్రారంభించి దిగుబడి కూడా వస్తుంది. అప్పుడు Thread-2
ప్రారంభించబడింది, ఇది కూడా దిగుబడిని ఇస్తుంది. మా వద్ద మరిన్ని థ్రెడ్లు లేవు మరియు Thread-2
దాని స్థానాన్ని చివరిగా అందించిన తర్వాత, థ్రెడ్ షెడ్యూలర్, 'హ్మ్, ఇక కొత్త థ్రెడ్లు ఏవీ లేవు. మనం క్యూలో ఎవరున్నారు? ఇంతకు ముందు దాని స్థానాన్ని ఎవరు ఇచ్చారు Thread-2
? అది ఉన్నట్లుంది Thread-1
. సరే, అంటే మేము దానిని అమలు చేయనివ్వండి. Thread-1
దాని పనిని పూర్తి చేసి, ఆపై థ్రెడ్ షెడ్యూలర్ దాని సమన్వయాన్ని కొనసాగిస్తుంది: 'సరే, Thread-1
పూర్తయింది. క్యూలో ఇంకెవరైనా ఉన్నారా?'. థ్రెడ్-0 క్యూలో ఉంది: ఇది ముందుగానే దాని స్థానాన్ని అందించిందిThread-1
. ఇది ఇప్పుడు దాని వంతు వచ్చింది మరియు పూర్తి చేయడానికి నడుస్తుంది. ఆపై షెడ్యూలర్ థ్రెడ్లను సమన్వయం చేయడం పూర్తి చేస్తాడు: 'సరే, Thread-2
మీరు ఇతర థ్రెడ్లకు లొంగిపోయారు మరియు ఇప్పుడు అవన్నీ పూర్తయ్యాయి. నువ్వు చివరిగా దిగుబడి సాధించావు కాబట్టి ఇప్పుడు నీ వంతు వచ్చింది. అప్పుడు Thread-2
పూర్తి చేయడానికి నడుస్తుంది. కన్సోల్ అవుట్పుట్ ఇలా కనిపిస్తుంది: థ్రెడ్-0 దాని స్థానాన్ని ఇతరులకు అందిస్తుంది. థ్రెడ్-0 అమలు చేయడం పూర్తయింది. థ్రెడ్-2 అమలు చేయడం పూర్తయింది. వాస్తవానికి, థ్రెడ్ షెడ్యూలర్ థ్రెడ్లను వేరే క్రమంలో ప్రారంభించవచ్చు (ఉదాహరణకు, 0-1-2కి బదులుగా 2-1-0), కానీ సూత్రం అలాగే ఉంటుంది.
నియమాలకు ముందు జరుగుతుంది
ఈరోజు మనం చివరిగా టచ్ చేయబోయేది ' ముందు జరుగుతుంది ' అనే భావన . మీకు ఇప్పటికే తెలిసినట్లుగా, జావాలో థ్రెడ్ షెడ్యూలర్ తమ పనులను నిర్వహించడానికి థ్రెడ్లకు సమయం మరియు వనరులను కేటాయించడంలో ఎక్కువ భాగం పని చేస్తుంది. థ్రెడ్లు యాదృచ్ఛిక క్రమంలో ఎలా అమలు చేయబడతాయో కూడా మీరు పదేపదే చూసారు, ఇది సాధారణంగా ఊహించడం అసాధ్యం. మరియు సాధారణంగా, మేము గతంలో చేసిన 'సీక్వెన్షియల్' ప్రోగ్రామింగ్ తర్వాత, మల్టీథ్రెడ్ ప్రోగ్రామింగ్ యాదృచ్ఛికంగా కనిపిస్తుంది. మల్టీథ్రెడ్ ప్రోగ్రామ్ యొక్క ప్రవాహాన్ని నియంత్రించడానికి మీరు అనేక పద్ధతులను ఉపయోగించవచ్చని మీరు ఇప్పటికే విశ్వసించారు. కానీ జావాలో మల్టీథ్రెడింగ్కు మరో స్తంభం ఉంది - 4 ' జరిగే-ముందు ' నియమాలు. ఈ నియమాలను అర్థం చేసుకోవడం చాలా సులభం. మనకు రెండు థ్రెడ్లు ఉన్నాయని ఊహించుకోండి -A
మరియుB
. ఈ థ్రెడ్లు ప్రతి ఒక్కటి కార్యకలాపాలను నిర్వహించగలవు 1
మరియు 2
. ప్రతి నియమంలో, ' A జరగడానికి-ముందు B ' అని చెప్పినప్పుడు , A
ఆపరేషన్కు ముందు థ్రెడ్ ద్వారా చేసిన అన్ని మార్పులు మరియు ఈ ఆపరేషన్ ఫలితంగా వచ్చే మార్పులు ఆపరేషన్ చేసినప్పుడు మరియు ఆ తర్వాత 1
థ్రెడ్కు కనిపిస్తాయి . ప్రతి నియమం మీరు మల్టీథ్రెడ్ ప్రోగ్రామ్ను వ్రాసినప్పుడు, కొన్ని సంఘటనలు ఇతరులకు 100% సమయం ముందు జరుగుతాయని మరియు ఆపరేషన్ సమయంలో థ్రెడ్ ఆపరేషన్ సమయంలో చేసిన మార్పుల గురించి ఎల్లప్పుడూ తెలుసుకుంటుందని హామీ ఇస్తుంది . వాటిని సమీక్షిద్దాం. B
2
2
B
A
1
నియమం 1.
మ్యూటెక్స్ని విడుదల చేయడం అదే మానిటర్ను మరొక థ్రెడ్ ద్వారా పొందే ముందు జరుగుతుంది . మీరు ఇక్కడ ప్రతిదీ అర్థం చేసుకున్నారని నేను భావిస్తున్నాను. ఒక వస్తువు లేదా తరగతి యొక్క మ్యూటెక్స్ ఒక థ్రెడ్ ద్వారా పొందబడితే., ఉదాహరణకు, థ్రెడ్ ద్వారాA
, మరొక థ్రెడ్ (థ్రెడ్ B
) అదే సమయంలో దాన్ని పొందదు. ఇది మ్యూటెక్స్ విడుదలయ్యే వరకు వేచి ఉండాలి.
నియమం 2.
పద్ధతిThread.start()
ముందు జరుగుతుంది Thread.run()
. మళ్ళీ, ఇక్కడ కష్టం ఏమీ లేదు. పద్ధతి లోపల కోడ్ని అమలు చేయడం ప్రారంభించడానికి run()
, మీరు తప్పనిసరిగా start()
థ్రెడ్లోని పద్ధతికి కాల్ చేయాలని మీకు ఇప్పటికే తెలుసు. ప్రత్యేకంగా, ప్రారంభ పద్ధతి, run()
పద్ధతి కాదు! పిలవబడే ముందు సెట్ చేయబడిన అన్ని వేరియబుల్స్ యొక్క విలువలు ఒకసారి ప్రారంభమైన తర్వాత పద్ధతి Thread.start()
లోపల కనిపిస్తాయి అని ఈ నియమం నిర్ధారిస్తుంది.run()
నియమం 3.
run()
పద్ధతి యొక్క ముగింపు పద్ధతి నుండి తిరిగి రావడానికి ముందు జరుగుతుందిjoin()
. మన రెండు థ్రెడ్లకు తిరిగి వెళ్దాం: A
మరియు B
. మేము పద్ధతిని పిలుస్తాము join()
, తద్వారా థ్రెడ్ దాని పనిని చేసే ముందు B
థ్రెడ్ పూర్తయ్యే వరకు వేచి ఉండటానికి హామీ ఇవ్వబడుతుంది . A
దీని అర్థం A ఆబ్జెక్ట్ యొక్క run()
పద్ధతి చివరి వరకు అమలు చేయబడుతుందని హామీ ఇవ్వబడింది. run()
మరియు థ్రెడ్ పద్ధతిలో జరిగే డేటాకు సంబంధించిన అన్ని మార్పులు A
థ్రెడ్లో కనిపిస్తాయని నూటికి నూరు శాతం హామీ ఇవ్వబడుతుంది, B
ఇది థ్రెడ్ తన పనిని పూర్తి చేయడానికి వేచి ఉండి A
, దాని స్వంత పనిని ప్రారంభించగలదు.
నియమం 4.
అదే వేరియబుల్ నుండి చదవడానికి ముందుvolatile
వేరియబుల్కు రాయడం జరుగుతుంది . మేము కీవర్డ్ని ఉపయోగించినప్పుడు volatile
, మేము ఎల్లప్పుడూ ప్రస్తుత విలువను పొందుతాము. ఒక long
లేదా double
(ఇక్కడ జరిగే సమస్యల గురించి మేము ముందుగా మాట్లాడాము) తో కూడా మీరు ఇప్పటికే అర్థం చేసుకున్నట్లుగా, కొన్ని థ్రెడ్లలో చేసిన మార్పులు ఇతర థ్రెడ్లకు ఎల్లప్పుడూ కనిపించవు. కానీ, వాస్తవానికి, అలాంటి ప్రవర్తన మనకు సరిపోని చాలా తరచుగా పరిస్థితులు ఉన్నాయి. థ్రెడ్లోని వేరియబుల్కు మనం విలువను కేటాయించామని అనుకుందాం A
:
int z;
….
z = 555;
మా B
థ్రెడ్ కన్సోల్లో వేరియబుల్ యొక్క విలువను ప్రదర్శిస్తే z
, అది 0ని సులభంగా ప్రదర్శిస్తుంది, ఎందుకంటే దానికి కేటాయించిన విలువ గురించి తెలియదు. z
కానీ రూల్ 4 హామీ ఇస్తుంది, ఒకవేళ మనం వేరియబుల్ని ఇలా ప్రకటిస్తే volatile
, ఒక థ్రెడ్లో దాని విలువకు మార్పులు ఎల్లప్పుడూ మరొక థ్రెడ్లో కనిపిస్తాయి. volatile
మునుపటి కోడ్కు పదాన్ని జోడిస్తే ...
volatile int z;
….
z = 555;
B
...తర్వాత మేము థ్రెడ్ 0ని ప్రదర్శించే పరిస్థితిని నివారిస్తాము. volatile
వేరియబుల్స్ నుండి చదవడానికి ముందు వాటికి వ్రాయడం జరుగుతుంది.
GO TO FULL VERSION