JavaRush /Java Blog /Random-TL /Pamamahala ng daloy. Ang pabagu-bagong keyword at ang yie...

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

Nai-publish sa grupo
Kamusta! Patuloy kaming nag-aaral ng multithreading, at ngayon ay makikilala namin ang isang bagong keyword - pabagu-bago ng isip at ang yield() na pamamaraan. Alamin natin kung ano ito :)

Pabagu-bago ng keyword

Kapag gumagawa ng mga multi-threaded na application, maaari tayong harapin ang dalawang seryosong problema. Una, sa panahon ng pagpapatakbo ng isang multi-threaded na application, maaaring i-cache ng iba't ibang mga thread ang mga halaga ng mga variable (pag-uusapan natin ang higit pa tungkol dito sa lecture na "Paggamit ng pabagu-bago ng isip" ). Posibleng binago ng isang thread ang halaga ng isang variable, ngunit hindi nakita ng pangalawa ang pagbabagong ito dahil gumagana ito sa sarili nitong naka-cache na kopya ng variable. Naturally, ang mga kahihinatnan ay maaaring maging seryoso. Isipin na ito ay hindi lamang isang uri ng "variable", ngunit, halimbawa, ang balanse ng iyong bank card, na biglang nagsimulang random na tumalon pabalik-balik :) Hindi masyadong kaaya-aya, tama? Pangalawa, sa Java, basahin at isulat ang mga operasyon sa mga patlang ng lahat ng uri maliban longat doubleatomic. Ano ang atomicity? Buweno, halimbawa, kung babaguhin mo ang halaga ng isang variable sa isang thread int, at sa isa pang thread nabasa mo ang halaga ng variable na ito, makakakuha ka ng alinman sa lumang halaga nito o ng bago - ang lumabas pagkatapos ng pagbabago sa thread 1. Walang lalabas na "intermediate options" doon Siguro. Gayunpaman, hindi ito gumagana sa longat . doubleBakit? Dahil ito ay cross-platform. Naaalala mo ba kung paano namin sinabi sa mga unang antas na ang prinsipyo ng Java ay "isinulat nang isang beses, gumagana sa lahat ng dako"? Ito ay cross-platform. Iyon ay, ang isang Java application ay tumatakbo sa ganap na magkakaibang mga platform. Halimbawa, sa mga operating system ng Windows, iba't ibang bersyon ng Linux o MacOS, at saanman gagana ang application na ito nang matatag. longat double- ang pinaka "mabigat" na primitive sa Java: tumitimbang sila ng 64 bits. At ang ilang 32-bit na platform ay hindi lamang nagpapatupad ng atomicity ng pagbabasa at pagsulat ng mga 64-bit na variable. Ang ganitong mga variable ay binabasa at isinulat sa dalawang operasyon. Una, ang unang 32 bits ay isinulat sa variable, pagkatapos ay isa pang 32. Alinsunod dito, sa mga kasong ito ay maaaring magkaroon ng problema. Ang isang thread ay nagsusulat ng ilang 64-bit na halaga sa isang variableХ, at ginagawa niya ito “sa dalawang hakbang.” Kasabay nito, sinusubukan ng pangalawang thread na basahin ang halaga ng variable na ito, at ginagawa ito nang tama sa gitna, kapag naisulat na ang unang 32 bits, ngunit ang mga pangalawa ay hindi pa naisulat. Bilang resulta, nagbabasa ito ng isang intermediate, hindi tamang halaga, at may naganap na error. Halimbawa, kung sa ganoong platform susubukan naming magsulat ng isang numero sa isang variable - 9223372036854775809 - sasakupin nito ang 64 bits. Sa binary form ito ay magiging ganito: 1000000000000000000000000000000000000000000000000000000000000001 Ang unang thread ay magsisimulang isulat ang numerong ito sa isang variable, at unang isusulat ang unang 000000000000000000000000 000000 00000 at pagkatapos ay ang pangalawa 32: 00000000000000000000000000000001 At ang pangalawang thread ay maaaring kumalas sa puwang na ito at basahin ang intermediate value ng variable - 10000000000000000000000000000000, ang unang 32 bits na naisulat na. Sa sistema ng decimal, ang numerong ito ay katumbas ng 2147483648. Ibig sabihin, gusto lang naming isulat ang numerong 9223372036854775809 sa isang variable, ngunit dahil sa katotohanan na ang operasyong ito sa ilang mga platform ay hindi atomic, nakuha namin ang "kaliwa" na numero 2147483648 , na hindi namin kailangan, out of nowhere. at hindi alam kung paano ito makakaapekto sa pagpapatakbo ng programa. Binabasa lang ng pangalawang thread ang halaga ng variable bago ito tuluyang naisulat, ibig sabihin, nakita nito ang unang 32 bits, ngunit hindi ang pangalawang 32 bits. Ang mga problemang ito, siyempre, ay hindi lumitaw kahapon, at sa Java sila ay nalutas gamit lamang ang isang keyword - pabagu-bago ng isip . Kung magdedeklara kami ng ilang variable sa aming programa na may salitang pabagu-bago...
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 atomiko. Kahit na ito ay 64-bit doubleo long.
  2. Hindi ito i-cache ng Java machine. Kaya ang sitwasyon kung kailan gumagana ang 10 thread sa kanilang mga lokal na kopya ay hindi kasama.
Ito ay kung paano malulutas ang dalawang napakaseryosong problema sa isang salita :)

paraan ng yield().

Tumingin na kami sa maraming paraan ng klase Thread, ngunit may isang mahalagang isa na magiging bago sa iyo. Ito ang yield() method . Isinalin mula sa Ingles bilang “give in.” At iyon mismo ang ginagawa ng pamamaraan! Pamamahala ng daloy.  Ang pabagu-bagong keyword at ang yield() na paraan - 2Kapag tinawag namin ang paraan ng pagbubunga sa isang thread, talagang sinasabi nito sa ibang mga thread: "Okay, guys, hindi ako nagmamadali, kaya kung mahalaga para sa sinuman sa inyo na makakuha ng oras ng CPU, kunin ito, ako ay hindi pa ngayon kailangan." 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() + "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();
   }
}
Sunud-sunod kaming gumagawa at naglulunsad ng tatlong thread - Thread-0, Thread-1at Thread-2. Thread-0nagsisimula muna at agad na nagbibigay daan sa iba. Pagkatapos nito ay nagsisimula ito Thread-1, at nagbibigay din ng daan. Pagkatapos nito, magsisimula ito Thread-2, na mas mababa din. Wala na kaming mga thread, at pagkatapos na Thread-2ang huli ay sumuko sa pwesto nito, ang thread scheduler ay tumingin: "So, wala nang mga bagong thread, sino ang mayroon tayo sa pila? Sino ang huling sumuko sa kanilang lugar noon Thread-2? Sa tingin ko ito ay Thread-1? Okay, so let it be done.” Thread-1ginagawa ang trabaho nito hanggang sa katapusan, pagkatapos nito ay patuloy na nag-coordinate ang thread scheduler: “Okay, natapos na ang Thread-1. May iba pa ba tayong nakapila?" Mayroong Thread-0 sa pila: agad itong sumuko bago ang Thread-1. Ngayon ang bagay ay dumating sa kanya, at siya ay isinasagawa hanggang sa wakas. Pagkatapos nito, natapos ng scheduler ang pag-coordinate ng mga thread: "Okay, Thread-2, nagbigay ka ng daan sa ibang mga thread, lahat sila ay gumana na. Ikaw ang huling nagbigay ng daan, kaya ngayon ang iyong turn." Pagkatapos nito, tumatakbo ang Thread-2 hanggang sa makumpleto. Ang output ng console ay magiging ganito: Ang Thread-0 ay nagbibigay-daan sa iba Ang Thread-1 ay nagbibigay-daan sa iba Ang Thread-2 ay nagbibigay-daan 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. Ang scheduler ng thread ay maaaring, siyempre, magpatakbo ng mga thread sa ibang pagkakasunud-sunod (halimbawa, 2-1-0 sa halip na 0-1-2), ngunit ang prinsipyo ay pareho.

Mangyayari-bago ang mga tuntunin

Ang huling bagay na tatalakayin natin ngayon ay ang mga prinsipyong " nangyayari bago ". Tulad ng alam mo na, sa Java, karamihan sa gawain ng paglalaan ng oras at mga mapagkukunan sa mga thread upang makumpleto ang kanilang mga gawain ay ginagawa ng thread scheduler. Nakita mo rin nang higit sa isang beses kung paano isinasagawa ang mga thread sa isang arbitrary na pagkakasunud-sunod, at kadalasan ay imposibleng mahulaan ito. At sa pangkalahatan, pagkatapos ng "sequential" na programming na ginawa namin dati, ang multithreading ay mukhang isang random na bagay. Tulad ng nakita mo na, ang pag-unlad ng isang multithreaded na programa ay maaaring kontrolin gamit ang isang buong hanay ng mga pamamaraan. Ngunit bilang karagdagan dito, sa Java multithreading mayroong isa pang "isla ng katatagan" - 4 na panuntunan na tinatawag na " nangyayari-bago ". Literal mula sa Ingles ito ay isinalin bilang "nangyayari bago", o "nangyayari bago". Ang kahulugan ng mga patakarang ito ay medyo simple upang maunawaan. Isipin na mayroon kaming dalawang thread - Aat B. Ang bawat isa sa mga thread na ito ay maaaring magsagawa ng mga operasyon 1at 2. At kapag sa bawat isa sa mga panuntunan ay sinabi nating " A happens-before B ", nangangahulugan ito na ang lahat ng mga pagbabagong ginawa ng thread Abago ang operasyon 1at ang mga pagbabago na kasama sa operasyong ito ay makikita ng thread Bsa oras na gumanap ang operasyon 2at pagkatapos maisagawa ang operasyon. Tinitiyak ng bawat isa sa mga panuntunang ito na kapag nagsusulat ng isang multi-threaded na programa, ang ilang mga kaganapan ay mangyayari bago ang iba 100% ng oras, at ang thread Bsa oras ng operasyon 2ay palaging nakakaalam ng mga pagbabago na Аginawa ng thread sa panahon ng operasyon. 1. Tingnan natin sila.

Panuntunan 1.

Nangyayari ang pagpapalabas ng mutex bago mangyari bago makuha ng isa pang thread ang parehong monitor. Well, ang lahat ay tila malinaw dito. Kung ang mutex ng isang bagay o klase ay nakuha ng isang thread, halimbawa, isang thread А, hindi ito makukuha ng isa pang thread (thread B) nang sabay-sabay. Kailangan mong maghintay hanggang sa mailabas ang mutex.

Panuntunan 2.

Thread.start() Ang mangyayari bago ang pamamaraan Thread.run(). Wala ring kumplikado. Alam mo na: upang ang code sa loob ng pamamaraan ay magsimulang magsagawa run(), kailangan mong tawagan ang pamamaraan sa thread start(). Ito ay kanya, at hindi ang pamamaraan mismo run()! Tinitiyak ng panuntunang ito na Thread.start()ang mga halaga ng lahat ng mga variable na itinakda bago ang pagpapatupad ay makikita sa loob ng pamamaraang nagsimulang isagawa run().

Panuntunan 3.

Nangyayari ang pagkumpleto ng pamamaraan run() bago lumabas ang pamamaraan join(). Bumalik tayo sa ating dalawang batis - Аat B. Tinatawag namin ang pamamaraan join()sa paraang Bdapat maghintay ang thread hanggang sa makumpleto Abago gawin ang gawain nito. Nangangahulugan ito na ang paraan run()ng object A ay tiyak na tatakbo hanggang sa pinakadulo. At ang lahat ng mga pagbabago sa data na nagaganap sa paraan run()ng thread Aay ganap na makikita sa thread Bkapag ito ay naghihintay para sa pagkumpleto Aat nagsimulang gumana mismo.

Panuntunan 4.

Ang pagsusulat sa isang pabagu-bagong variable ay nangyayari bago magbasa mula sa parehong variable. Sa pamamagitan ng paggamit ng pabagu-bagong keyword, sa katunayan, palagi nating makukuha ang kasalukuyang halaga. Kahit na sa kaso ng longat double, ang mga problemang napag-usapan kanina. Tulad ng naiintindihan mo na, ang mga pagbabagong ginawa sa ilang mga thread ay hindi palaging nakikita ng iba pang mga thread. Ngunit, siyempre, napakadalas na may mga sitwasyon kung saan ang pag-uugali ng programa ay hindi angkop sa amin. Sabihin nating nagtalaga kami ng isang halaga sa isang variable sa isang thread A:
int z;.

z= 555;
Kung ang aming thread Bay magpi-print ng halaga ng isang variable zsa console, madali itong mag-print ng 0 dahil hindi nito alam ang tungkol sa halagang itinalaga dito. Kaya, ginagarantiyahan kami ng Panuntunan 4: kung idedeklara mo ang isang variable zbilang pabagu-bago, ang mga pagbabago sa mga halaga nito sa isang thread ay palaging makikita sa isa pang thread. Kung idaragdag natin ang salitang pabagu-bago ng isip sa nakaraang code...
volatile int z;.

z= 555;
...ang sitwasyon kung saan ang stream Bay maglalabas ng 0 sa console ay hindi kasama. Ang pagsusulat sa mga pabagu-bagong variable ay nangyayari bago magbasa mula sa mga ito.
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION