JavaRush /Java блогы /Random-KK /Ағынды басқару. Тұрақты кілт сөз және yield() әдісі

Ағынды басқару. Тұрақты кілт сөз және yield() әдісі

Топта жарияланған
Сәлеметсіз бе! Біз көп ағынды оқуды жалғастырамыз, және бүгін біз жаңа кілт сөзбен - volatile және yield() әдісімен танысамыз. Оның не екенін анықтайық :)

Түйінді сөз өзгермелі

Көп ағынды қосымшаларды жасау кезінде біз екі күрделі мәселеге тап болуымыз мүмкін. Біріншіден, көп ағынды қосымшаның жұмысы кезінде әртүрлі ағындар айнымалы мәндерді кэштей алады (бұл туралы «Ұшқышты пайдалану» дәрісінде толығырақ айтатын боламыз ). Бір ағын айнымалының мәнін өзгерткен болуы мүмкін, бірақ екіншісі бұл өзгерісті көрмеді, себебі ол айнымалының өзінің кэштелген көшірмесімен жұмыс істеді. Әрине, салдары ауыр болуы мүмкін. Елестетіп көріңізші, бұл жай ғана «айнымалы» емес, бірақ, мысалы, кенеттен кездейсоқ алға-артқа секіре бастаған банктік картаңыздың балансы :) Өте жағымды емес, солай емес пе? Екіншіден, Java тілінде атомды қоспағанда , барлық түрдегі өрістерде оқу және жазу әрекеттері орындалады . longdouble Атомдық дегеніміз не? Мысалы, егер сіз бір ағындағы айнымалының мәнін өзгертсеңіз intжәне басқа ағында осы айнымалының мәнін оқысаңыз, сіз оның ескі мәнін немесе жаңасын аласыз - өзгергеннен кейін пайда болған мән. жіп 1. Онда «аралық опциялар» пайда болмайды Мүмкін. Дегенмен, бұл longжәне -мен doubleжұмыс істемейді . Неліктен? Өйткені бұл кросс-платформа. Java принципі «бір рет жазылады, барлық жерде жұмыс істейді» деп бірінші деңгейде қалай айтқанымыз есіңізде ме? Бұл кросс-платформа. Яғни, Java қолданбасы мүлдем басқа платформаларда жұмыс істейді. Мысалы, Windows операциялық жүйелерінде, Linux немесе MacOS-тың әртүрлі нұсқаларында және барлық жерде бұл қолданба тұрақты жұмыс істейді. longжәне double- Java тіліндегі ең «ауыр» примитивтер: олардың салмағы 64 бит. Ал кейбір 32-биттік платформалар 64-биттік айнымалыларды оқу мен жазудың атомдылығын жүзеге асырмайды. Мұндай айнымалылар екі операцияда оқылады және жазылады. Алдымен айнымалыға алғашқы 32 бит жазылады, содан кейін тағы 32. Сәйкесінше, бұл жағдайларда мәселе туындауы мүмкін. Бір ағын айнымалыға 64-биттік мән жазадыХ, және ол мұны «екі қадаммен» жасайды. Сонымен қатар, екінші ағын осы айнымалының мәнін оқуға тырысады және оны дәл ортасында, бірінші 32 бит жазылған, бірақ екіншісі әлі жазылмаған кезде жасайды. Нәтижесінде ол аралық, қате мәнді оқиды және қате пайда болады. Мысалы, егер мұндай платформада айнымалыға санды жазуға тырыссақ - 9223372036854775809 - ол 64 бит алады. Екілік пішінде ол келесідей болады: 100000000000000000000000000000000000000000000000000000000000000001 Бірінші ағын бұл санды айнымалыға жаза бастайды және алдымен бірінші бит жазады: 0000000000000000 00000000 00000, содан кейін екіншісі 32: 0000000000000000000000000000000001 Ал екінші жіп осы бос орынға сынады және айнымалының аралық мәнін оқыңыз - 1000000000000000000000000000000000, жазылған алғашқы 32 бит. Ондық жүйеде бұл сан 2147483648-ге тең. Яғни, біз жай ғана 9223372036854775809 санын айнымалыға жазғымыз келді, бірақ кейбір платформалардағы бұл операция атомдық емес болғандықтан, біз 2147483648 «сол» санын алдық. , ол бізге қажет емес, күтпеген жерден. және бұл бағдарламаның жұмысына қалай әсер ететіні белгісіз. Екінші ағын айнымалының мәнін ақырында жазылғанға дейін оқыды, яғни ол бірінші 32 битті көрді, бірақ екінші 32 бит емес. Бұл мәселелер, әрине, кеше пайда болған жоқ, және Java-да олар бір ғана кілт сөздің көмегімен шешіледі - volatile . Егер біз бағдарламамызда қандай да бір айнымалыны volatile сөзімен жарияласақ...
public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…Бұл дегеніміз:
  1. Ол әрқашан атомдық түрде оқылады және жазылады. Тіпті 64 биттік doubleнемесе long.
  2. Java құрылғысы оны кэштемейді. Осылайша, 10 ағын олардың жергілікті көшірмелерімен жұмыс істейтін жағдай алынып тасталды.
Бір сөзбен екі өте күрделі мәселе осылай шешіледі :)

yield() әдісі

Біз сыныптың көптеген әдістерін қарастырдық Thread, бірақ сіз үшін жаңа болатын бір маңыздысы бар. Бұл yield() әдісі . Ағылшын тілінен «беру» деп аударылған. Бұл әдіс дәл осылай жасайды! Ағынды басқару.  Тұрақты кілт сөз және yield() әдісі - 2Біз жіптегі кірістілік әдісін шақырған кезде, ол басқа ағындарға былай дейді: «Жарайды, балалар, мен асығыс емеспін, сондықтан сіздердің кез келгеніңіз үшін процессордың уақытын алу маңызды болса, оны алыңыз, мен шұғыл емес». Бұл қалай жұмыс істейтінінің қарапайым мысалы:
public class ThreadExample extends Thread {

   public ThreadExample() {
       this.start();
   }

   public void run() {

       System.out.println(Thread.currentThread().getName() + "give way 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 аяқталды. Бізде басқа біреу бар ма?» Кезекте Thread-0 бар: ол Thread-1 алдында өз орнын бірден тастады. Енді іс оның басына келіп, соңына дейін жүргізіліп жатыр. Осыдан кейін жоспарлаушы ағындарды үйлестіруді аяқтайды: «Жарайды, Thread-2, сіз басқа ағындарға жол бердіңіз, олардың барлығы жұмыс істеп қойған. Сіз соңғы болып жол бердіңіз, енді сіздің кезегіңіз». Осыдан кейін Thread-2 аяқталуға дейін жұмыс істейді. Консоль шығысы келесідей болады: Thread-0 басқаларға жол береді Thread-1 басқаларға жол береді Thread-2 басқаларға жол береді Thread-1 орындалуды аяқтады. Thread-0 орындалуды аяқтады. Thread-2 орындалуды аяқтады. Ағынды жоспарлаушы, әрине, ағындарды басқа ретпен іске қоса алады (мысалы, 0-1-2 орнына 2-1-0), бірақ принцип бірдей.

Ережелерден бұрын орын алады

Бүгін біз қозғайтын соңғы нәрсе - « бұрын болады » қағидалары. Өздеріңіз білетіндей, Java тілінде тапсырмаларды орындау үшін ағындарға уақыт пен ресурстарды бөлу жұмысының көп бөлігін ағынды жоспарлаушы орындайды. Сіз сондай-ақ ағындардың ерікті тәртіпте қалай орындалатынын бірнеше рет көрдіңіз және көбінесе оны болжау мүмкін емес. Жалпы, біз бұрын жасаған «тізбекті» бағдарламалаудан кейін көп ағынды кездейсоқ нәрсе сияқты көрінеді. Көріп отырғаныңыздай, көп ағынды бағдарламаның орындалу барысын әдістердің бүкіл жиынтығы арқылы басқаруға болады. Бұған қоса, Java көп ағынында тағы бір «тұрақтылық аралы» бар - « болды-бұрын » деп аталатын 4 ереже. Ағылшын тілінен сөзбе-сөз бұл «бұрын болады» немесе «бұрын болады» деп аударылады. Бұл ережелердің мағынасын түсіну өте қарапайым. Бізде екі жіп бар деп елестетіңіз - Aжәне B. Бұл ағындардың әрқайсысы операцияларды орындай алады 1және 2. Ал ережелердің әрқайсысында « А болады-В алдында боладыA » десек, бұл операцияға дейін жіппен жасалған барлық өзгерістер және осы операция әкелген өзгерістер операция орындалған кезде 1жіпке көрінетінін білдіреді және операция жасалғаннан кейін. Осы ережелердің әрқайсысы көп ағынды бағдарламаны жазу кезінде кейбір оқиғалар басқалардан бұрын 100% болатынын және операция кезінде жіптің операция кезінде жасаған өзгерістері туралы әрқашан хабардар болуын қамтамасыз етеді. . Оларды қарастырайық. B2B2А1

1-ереже.

Мутексті шығару басқа ағын бірдей мониторды алғанға дейін орын алады . Мұнда бәрі түсінікті сияқты. Егер нысанның немесе сыныптың мутексі бір ағынмен алынса, мысалы, жіп А, басқа ағын (жіп B) оны бір уақытта ала алмайды. Мутекс шыққанша күту керек.

2-ереже.

Бұл әдіске Thread.start() дейін болады Thread.run() . Ештеңе де күрделі емес. Сіз бұрыннан білесіз: әдіс ішіндегі code орындай бастау үшін run()ағындағы әдісті шақыру керек start(). Бұл әдістің өзі емес, оныкі run()! Бұл ереже Thread.start()орындауға дейін орнатылған барлық айнымалы мәндердің орындау басталған әдіс ішінде көрінетінін қамтамасыз етеді run().

3-ереже.

Әдістің аяқталуы әдіс шыққанға run() дейін боладыjoin() . Екі ағынымызға оралайық - Ажәне B. Біз әдісті жіп өз жұмысын орындамас бұрын аяқталуын күтетіндей join()етіп шақырамыз . Бұл А нысанының әдісі міндетті түрде соңына дейін жұмыс істейтінін білдіреді . Жіп әдісінде орын алатын деректердегі барлық өзгерістер ол аяқталуын күтіп , өздігінен жұмыс істей бастағанда ағында толығымен көрінеді . BArun()run()ABA

4-ереже.

Құбылмалы айнымалыға жазу сол айнымалыдан оқуға дейін болады . Тұрақты кілт сөзді пайдалану арқылы біз әрқашан ағымдағы мәнді аламыз. longТіпті және жағдайында да double, проблемалары бұрын талқыланған. Түсінгеніңіздей, кейбір ағындарда жасалған өзгертулер басқа ағындарға әрқашан көрінбейді. Бірақ, әрине, мұндай бағдарлама мінез-құлқы бізге сәйкес келмейтін жағдайлар жиі кездеседі. Біз ағындағы айнымалыға мән бердік делік A:
int z;.

z= 555;
Егер біздің ағын консольге Bайнымалының мәнін басып шығаратын болса z, ол 0-ді оңай басып шығара алады, себебі ол оған тағайындалған мән туралы білмейді. Сонымен, 4-ереже бізге кепілдік береді: егер айнымалыны өзгермелі деп жарияласаңыз z, оның бір ағындағы мәндеріндегі өзгерістер басқа ағында әрқашан көрінеді. Алдыңғы codeқа ұшпа сөзін қоссақ...
volatile int z;.

z= 555;
B... консольге ағын 0 шығаратын жағдай алынып тасталды. Тұрақты айнымалыларға жазу олардан оқылмай тұрып орын алады.
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION