CodeGym /Java Blog /Random /Pamamahala ng mga thread. Ang pabagu-bagong keyword at an...
John Squirrels
Antas
San Francisco

Pamamahala ng mga thread. Ang pabagu-bagong keyword at ang yield() na paraan

Nai-publish sa grupo
Hi! Ipinagpapatuloy namin ang aming pag-aaral ng multithreading. Ngayon ay malalaman natin ang volatilekeyword at ang yield()pamamaraan. Sumisid tayo :)

Ang pabagu-bagong keyword

Kapag gumagawa ng mga multithreaded na application, maaari tayong magkaroon ng dalawang seryosong problema. Una, kapag tumatakbo ang isang multithreaded na application, maaaring i-cache ng iba't ibang thread ang mga halaga ng mga variable (napag-usapan na natin ito sa aralin na pinamagatang 'Using volatile' ). Maaari kang magkaroon ng sitwasyon kung saan binabago ng isang thread ang halaga ng isang variable, ngunit hindi nakikita ng pangalawang thread ang pagbabago, dahil gumagana ito sa naka-cache na kopya nito ng variable. Natural, ang mga kahihinatnan ay maaaring maging seryoso. Ipagpalagay na ito ay hindi lamang anumang lumang variable ngunit sa halip ang iyong balanse sa bank account, na biglang nagsisimula nang random na tumalon pataas at pababa :) Hindi iyon mukhang masaya, tama ba? Pangalawa, sa Java, ang mga operasyon upang basahin at isulat ang lahat ng mga primitive na uri,longdouble, ay atomic. Buweno, halimbawa, kung babaguhin mo ang halaga ng isang intvariable sa isang thread, at sa isa pang thread nabasa mo ang halaga ng variable, makukuha mo ang lumang halaga nito o ang bago, ibig sabihin, ang halaga na nagresulta mula sa pagbabago sa thread 1. Walang 'intermediate values'. Gayunpaman, hindi ito gumagana sa longs at doubles. Bakit? Dahil sa suporta sa cross-platform. Tandaan sa mga panimulang antas na sinabi namin na ang gabay na prinsipyo ng Java ay 'magsulat ng isang beses, tumakbo kahit saan'? Iyon ay nangangahulugan ng cross-platform na suporta. Sa madaling salita, tumatakbo ang isang Java application sa lahat ng uri ng iba't ibang platform. Halimbawa, sa mga operating system ng Windows, iba't ibang bersyon ng Linux o MacOS. Ito ay tatakbo nang walang sagabal sa kanilang lahat. Pagtimbang sa isang 64 bits,longdoubleay ang 'pinakamabigat' primitives sa Java. At ang ilang partikular na 32-bit na platform ay hindi lang nagpapatupad ng atomic na pagbasa at pagsulat ng 64-bit na mga variable. Ang ganitong mga variable ay binabasa at isinulat sa dalawang operasyon. Una, ang unang 32 bits ay isinusulat sa variable, at pagkatapos ay isa pang 32 bits ang isinusulat. Bilang resulta, maaaring magkaroon ng problema. Ang isang thread ay nagsusulat ng ilang 64-bit na halaga sa isang Xvariable at ginagawa ito sa dalawang operasyon. Kasabay nito, sinusubukan ng pangalawang thread na basahin ang halaga ng variable at ginagawa ito sa pagitan ng dalawang operasyong iyon — kapag naisulat na ang unang 32 bits, ngunit ang pangalawang 32 bits ay wala pa. Bilang resulta, nagbabasa ito ng intermediate, hindi tamang halaga, at mayroon kaming bug. Halimbawa, kung sa ganoong platform susubukan naming isulat ang numero sa isang 9223372036854775809 sa isang variable, sasakupin nito ang 64 bits. Sa binary form, ganito ang hitsura: 10000000000000000000000000000000000000000000000000000000001 Ang unang thread ay magsisimulang isulat ang numero sa variable. Sa una, sinusulat nito ang unang 32 bits (1000000000000000000000000000000) at pagkatapos ay ang pangalawang 32 bits (0000000000000000000000000000001) At ang pangalawang thread ay maaaring maipit sa pagitan ng mga operasyong ito, na binabasa ang intermediate na halaga ng variable (100000000000000000000000000000000), na siyang unang 32 bit na naisulat na. Sa sistema ng decimal, ang numerong ito ay 2,147,483,648. Sa madaling salita, gusto lang naming isulat ang numerong 9223372036854775809 sa isang variable, ngunit dahil sa katotohanan na ang operasyong ito ay hindi atomic sa ilang mga platform, mayroon kaming masamang numero na 2,147,483,648, na nanggaling sa wala at magkakaroon ng hindi kilalang epekto ang programa. Binabasa lang ng pangalawang thread ang halaga ng variable bago ito natapos na isulat, ibig sabihin, nakita ng thread ang unang 32 bits, ngunit hindi ang pangalawang 32 bits. Siyempre, ang mga problemang ito ay hindi lumitaw kahapon. Niresolba ng Java ang mga ito gamit ang isang keyword: volatile. Kung gagamitin natin angvolatilekeyword kapag nagdedeklara ng ilang variable sa aming programa...

public class Main {

   public volatile long x = 2222222222222222222L;

   public static void main(String[] args) {

   }
}
…ibig sabihin nito ay:
  1. Ito ay palaging babasahin at isusulat nang atomically. Kahit na ito ay isang 64-bit doubleo long.
  2. Hindi ito i-cache ng Java machine. Kaya hindi ka magkakaroon ng sitwasyon kung saan gumagana ang 10 thread sa sarili nilang mga lokal na kopya.
Kaya, dalawang napakaseryosong problema ay malulutas sa isang salita lamang :)

Ang paraan ng yield().

Nasuri na namin ang marami sa mga Threadpamamaraan ng klase, ngunit may mahalagang isa na magiging bago sa iyo. Ito ang yield()pamamaraan . At ginagawa nito kung ano mismo ang ipinahihiwatig ng pangalan nito! Pamamahala ng mga thread.  Ang pabagu-bagong keyword at ang yield() na paraan - 2Kapag tinawag namin ang yieldpamamaraan sa isang thread, talagang nakikipag-usap ito sa iba pang mga thread: 'Hey, guys. Hindi ako nagmamadaling pumunta kahit saan, kaya kung mahalaga para sa sinuman sa inyo na makakuha ng oras ng processor, kunin ito — makakapaghintay ako'. Narito ang isang simpleng halimbawa kung paano ito gumagana:

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();
   }
}
Sunud-sunod kaming gumagawa at nagsimula ng tatlong thread: Thread-0, Thread-1, at Thread-2. Thread-0nagsisimula muna at agad na nagbubunga sa iba. Pagkatapos Thread-1ay nagsimula at nagbubunga din. Pagkatapos Thread-2ay nagsimula, na nagbubunga din. Wala na kaming mga thread, at pagkatapos Thread-2na ibigay ang lugar nito sa huli, sinabi ng thread scheduler, 'Hmm, wala nang mga bagong thread. Sino ang kasama natin sa pila? Sino ang nagbigay ng pwesto noon Thread-2? Mukhang ito ay Thread-1. Okay, ibig sabihin, hahayaan natin itong tumakbo'. Thread-1kumpletuhin ang trabaho nito at pagkatapos ay ipagpapatuloy ng thread scheduler ang koordinasyon nito: 'Okay, Thread-1tapos na. May iba pa ba tayong nakapila?'. Ang Thread-0 ay nasa pila: ito ay nagbigay ng lugar nito kaninaThread-1. Nakakuha na ito ng pagkakataon at tumatakbo hanggang sa matapos. Pagkatapos ay tatapusin ng scheduler ang pag-coordinate ng mga thread: 'Okay, Thread-2, sumuko ka sa ibang mga thread, at tapos na ang lahat ngayon. Ikaw ang huling sumuko, kaya ngayon ang iyong turn'. Pagkatapos Thread-2ay tumatakbo hanggang sa matapos. Ang output ng console ay magiging ganito: Ang Thread-0 ay nagbubunga ng lugar nito sa iba Ang Thread-1 ay nagbubunga ng lugar nito sa iba Ang Thread-2 ay nagbubunga ng lugar nito sa iba Ang Thread-1 ay tapos na sa pagpapatupad. Ang thread-0 ay tapos nang isagawa. Ang Thread-2 ay tapos na i-execute. Siyempre, maaaring simulan ng thread scheduler ang mga thread sa ibang pagkakasunud-sunod (halimbawa, 2-1-0 sa halip na 0-1-2), ngunit ang prinsipyo ay nananatiling pareho.

Nangyayari-bago ang mga tuntunin

Ang huling bagay na tatalakayin natin ngayon ay ang konsepto ng ' nangyayari bago '. Tulad ng alam mo na, sa Java ang thread scheduler ay gumaganap ng karamihan sa gawaing kasangkot sa paglalaan ng oras at mga mapagkukunan sa mga thread upang maisagawa ang kanilang mga gawain. Paulit-ulit mo ring nakita kung paano isinasagawa ang mga thread sa isang random na pagkakasunud-sunod na kadalasang imposibleng mahulaan. At sa pangkalahatan, pagkatapos ng 'sequential' na programming na ginawa namin dati, mukhang random ang multithreaded programming. Naniwala ka na na maaari kang gumamit ng maraming pamamaraan upang kontrolin ang daloy ng isang multithreaded na programa. Ngunit ang multithreading sa Java ay may isa pang haligi — ang 4 na ' happens-before ' na mga panuntunan. Ang pag-unawa sa mga patakarang ito ay medyo simple. Isipin na mayroon kaming dalawang thread - AatB. Ang bawat isa sa mga thread na ito ay maaaring magsagawa ng mga operasyon 1at 2. Sa bawat panuntunan, kapag sinabi naming ' A happens-before B ', ang ibig naming sabihin ay ang lahat ng pagbabagong ginawa ng thread Abago ang operasyon 1at ang mga pagbabagong nagreresulta mula sa operasyong ito ay makikita ng thread Bkapag 2isinagawa ang operasyon at pagkatapos nito. Ginagarantiyahan ng bawat panuntunan na kapag sumulat ka ng isang multithreaded na programa, ang ilang partikular na kaganapan ay magaganap bago ang iba 100% ng oras, at na sa oras ng pagpapatakbo ng 2thread Bay palaging malalaman ang mga pagbabagong Aginawa ng thread sa panahon ng operasyon 1. Suriin natin ang mga ito.

Panuntunan 1.

Ang pagpapalabas ng isang mutex ay nangyayari bago ang parehong monitor ay nakuha ng isa pang thread. I think naiintindihan mo lahat dito. Kung ang mutex ng isang bagay o klase ay nakuha ng isang thread., halimbawa, sa pamamagitan ng thread , hindi ito makukuha ng Aisa pang thread (thread ) nang sabay-sabay. BDapat itong maghintay hanggang sa mailabas ang mutex.

Panuntunan 2.

Ang Thread.start()pamamaraan ay nangyayari bago Thread.run() . Muli, walang mahirap dito. Alam mo na na upang simulan ang pagpapatakbo ng code sa loob ng run()pamamaraan, dapat mong tawagan ang start()pamamaraan sa thread. Sa partikular, ang paraan ng pagsisimula, hindi ang run()pamamaraan mismo! Tinitiyak ng panuntunang ito na ang mga halaga ng lahat ng mga variable na itinakda bago Thread.start()ay tinatawag ay makikita sa loob ng run()pamamaraan kapag nagsimula na.

Panuntunan 3.

Ang pagtatapos ng run()pamamaraan ay nangyayari bago ang pagbabalik mula sa join()pamamaraan. Bumalik tayo sa ating dalawang thread: Aat B. Tinatawag namin ang join()pamamaraan upang ang thread Bay garantisadong maghintay para sa pagkumpleto ng thread Abago ito gumana. Nangangahulugan ito na ang pamamaraan ng A object run()ay garantisadong tatakbo hanggang sa dulo. At lahat ng pagbabago sa data na nangyayari sa run()paraan ng thread Aay isang-daang porsyento na garantisadong makikita sa thread Bkapag tapos na itong naghihintay na Amatapos ang thread nito para makapagsimula ito ng sarili nitong gawain.

Panuntunan 4.

Ang pagsusulat sa isang volatilevariable ay nangyayari bago basahin mula sa parehong variable na iyon. Kapag ginamit namin ang volatilekeyword, talagang palagi naming nakukuha ang kasalukuyang halaga. Kahit na may isang longo double(nag-usap kami kanina tungkol sa mga problema na maaaring mangyari dito). Tulad ng naiintindihan mo na, ang mga pagbabagong ginawa sa ilang mga thread ay hindi palaging nakikita ng iba pang mga thread. Ngunit, siyempre, may napakadalas na mga sitwasyon kung saan ang gayong pag-uugali ay hindi angkop sa atin. Ipagpalagay na nagtalaga kami ng isang halaga sa isang variable sa thread A:

int z;

….

z = 555;
Kung Bdapat ipakita ng aming thread ang halaga ng zvariable sa console, madali itong magpakita ng 0, dahil hindi nito alam ang tungkol sa nakatalagang halaga. Ngunit ginagarantiyahan ng Panuntunan 4 na kung idedeklara namin ang zvariable bilang volatile, ang mga pagbabago sa halaga nito sa isang thread ay palaging makikita sa isa pang thread. Kung idagdag natin ang salita volatilesa nakaraang code...

volatile int z;

….

z = 555;
...pagkatapos ay pinipigilan namin ang sitwasyon kung saan Bmaaaring magpakita ang thread ng 0. Ang pagsusulat sa volatilemga variable ay nangyayari bago basahin ang mga ito.
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION