Java 101: Razumijevanje Java niti, 3. dio: Zakazivanje niti i pričekajte / obavijestite

Ovog mjeseca nastavljam četverodijelno upoznavanje Java niti usredotočujući se na planiranje niti, mehanizam čekanja / obavještavanja i prekidanje niti. Istražit ćete kako JVM ili planer niti operativnog sustava odabire sljedeću nit za izvršenje. Kao što ćete otkriti, prioritet je važan za odabir planera niti. Ispitat ćete kako nit čeka dok ne primi obavijest od druge niti prije nego što nastavi s izvršavanjem i naučit ćete kako koristiti mehanizam čekanja / obavještavanja za koordinaciju izvršavanja dviju niti u odnosu proizvođača i potrošača. Napokon ćete naučiti kako prerano probuditi uspavanu nit ili nit na čekanju za prekidanje niti ili druge zadatke. Također ću vas naučiti kako nit koja niti spava niti čeka niti otkriva zahtjev za prekidom iz druge niti.

Imajte na umu da je ovaj članak (dio arhiva JavaWorld) ažuriran novim popisima kodova i izvornim kodom za preuzimanje u svibnju 2013.

Razumijevanje Java niti - pročitajte cijelu seriju

  • Dio 1: Upoznavanje niti i pokretačkih programa
  • Dio 2: Sinkronizacija
  • Dio 3: Zakazivanje niti, čekanje / obavještavanje i prekidanje niti
  • Dio 4: Skupine niti, volatilnost, lokalne varijable niti, mjerači vremena i odumiranje niti

Zakazivanje niti

U idealiziranom svijetu sve programske niti imale bi vlastite procesore na kojima bi se mogle pokretati. Dok ne dođe vrijeme kada računala imaju tisuće ili milijune procesora, niti često moraju dijeliti jedan ili više procesora. Ili JVM ili operativni sustav osnovne platforme dešifrira kako podijeliti resurs procesora među nitima - zadatak poznat kao planiranje niti . Taj dio JVM-a ili operativnog sustava koji izvodi raspoređivanje niti je planer niti .

Napomena: Da bih pojednostavio raspravu o planiranju niti, usredotočujem se na planiranje niti u kontekstu jednog procesora. Ovu raspravu možete ekstrapolirati na više procesora; Taj zadatak prepuštam vama.

Zapamtite dvije važne točke o planiranju niti:

  1. Java ne prisiljava VM da planira niti na određeni način ili sadrži planer niti. To podrazumijeva planiranje niti ovisno o platformi. Stoga morate paziti pri pisanju Java programa čije ponašanje ovisi o načinu rasporeda niti i moraju dosljedno raditi na različitim platformama.
  2. Srećom, prilikom pisanja Java programa, morate razmisliti o tome kako Java raspoređuje niti samo kada barem jedna od niti vašeg programa intenzivno koristi procesor tijekom dužih vremenskih razdoblja, a međuvrijedni rezultati izvršavanja te niti pokažu se važnima. Na primjer, aplet sadrži nit koja dinamički stvara sliku. Povremeno želite da nit za slikanje crta trenutni sadržaj te slike kako bi korisnik mogao vidjeti kako slika napreduje. Da biste osigurali da nit izračuna ne monopolizira procesor, razmislite o rasporedu niti.

Ispitajte program koji stvara dvije procesorski intenzivne niti:

Popis 1. SchedDemo.java

// SchedDemo.java class SchedDemo { public static void main (String [] args) { new CalcThread ("CalcThread A").start (); new CalcThread ("CalcThread B").start (); } } class CalcThread extends Thread { CalcThread (String name) { // Pass name to Thread layer. super (name); } double calcPI () { boolean negative = true; double pi = 0.0; for (int i = 3; i < 100000; i += 2) { if (negative) pi -= (1.0 / i); else pi += (1.0 / i); negative = !negative; } pi += 1.0; pi *= 4.0; return pi; } public void run () { for (int i = 0; i < 5; i++) System.out.println (getName () + ": " + calcPI ()); } }

SchedDemostvara dvije niti koje svaka izračunavaju vrijednost pi (pet puta) i ispisuju svaki rezultat. Ovisno o tome kako vaša implementacija JVM zakazuje niti, možda ćete vidjeti izlaz sličan sljedećem:

CalcThread A: 3.1415726535897894 CalcThread B: 3.1415726535897894 CalcThread A: 3.1415726535897894 CalcThread A: 3.1415726535897894 CalcThread B: 3.1415726535897894 CalcThread A: 3.1415726535897894 CalcThread A: 3.1415726535897894 CalcThread B: 3.1415726535897894 CalcThread B: 3.1415726535897894 CalcThread B: 3.1415726535897894

Prema gore navedenom izlazu, planer niti dijeli procesor između obje niti. Međutim, mogli biste vidjeti izlaz sličan ovome:

CalcThread A: 3.1415726535897894 CalcThread A: 3.1415726535897894 CalcThread A: 3.1415726535897894 CalcThread A: 3.1415726535897894 CalcThread A: 3.1415726535897894 CalcThread B: 3.1415726535897894 CalcThread B: 3.1415726535897894 CalcThread B: 3.1415726535897894 CalcThread B: 3.1415726535897894 CalcThread B: 3.1415726535897894

Gornji izlaz prikazuje planer niti koji daje prednost jednoj niti nad drugom. Dva gornja izlaza ilustriraju dvije opće kategorije planera niti: zeleni i izvorni. Istražit ću njihove razlike u ponašanju u sljedećim odjeljcima. Dok raspravljam o svakoj kategoriji, pozivam se na stanja niti kojih ima četiri:

  1. Početno stanje: Program je stvorio objekt niti niti, ali nit još ne postoji jer start()metoda objekta niti još nije pozvana.
  2. Izvodljivo stanje: Ovo je zadano stanje niti. Nakon završetka poziva start(), nit postaje pokretan bez obzira radi li se ta nit, odnosno, koristi procesor. Iako se mnoge niti mogu pokrenuti, trenutno se izvodi samo jedna. Planeri za određivanje niti određuju koju nit koja se može pokrenuti dodijeliti procesoru.
  3. Blokirani stanje: Kad nit izvršava sleep(), wait()ili join()metoda, kad nit pokušaji da se čitanje podataka još nisu dostupne mreže, a kada se nit čeka steći zaključavanje, koji nit je u blokiranog stanja: ona ne radi, niti u poziciji za trčanje. (Vjerojatno se možete sjetiti drugih vremena kada bi nit čekao da se nešto dogodi.) Kad se blokirana nit deblokira, ta se nit premješta u izvodljivo stanje.
  4. Završno stanje: Jednom kada izvršenje napusti run()metodu niti, ta je nit u završnom stanju. Drugim riječima, nit prestaje postojati.

Kako planer niti odabire koju će se nit za pokretanje pokrenuti? Počinjem odgovarati na to pitanje dok raspravljam o rasporedu zelenih niti. Završavam odgovor dok raspravljam o planiranju izvornih niti.

Zakazivanje zelenih niti

Nisu svi operativni sustavi, na primjer drevni sustav Microsoft Windows 3.1, koji podržavaju niti. Za takve sustave Sun Microsystems može dizajnirati JVM koji dijeli svoju jedinu nit izvršenja u više niti. JVM (ne operativni sustav osnovne platforme) pruža logiku navoja i sadrži planer niti. JVM niti su zelene niti ili korisničke niti .

JVM-ov planer niti planira zelene niti prema prioritetu - relativnu važnost niti, koju izrazite kao cijeli broj iz dobro definiranog raspona vrijednosti. Tipično, JVM-ov planer niti odabire nit s najvećim prioritetom i dopušta izvođenju te niti dok se ne završi ili blokira. U to vrijeme planer niti odabire nit sljedećeg najvišeg prioriteta. Ta nit (obično) radi dok se ne završi ili blokira. Ako se, dok se nit izvodi, nit višeg prioriteta deblokira (možda je isteklo vrijeme mirovanja niti višeg prioriteta), planer niti sprečava ili prekida nit nižeg prioriteta i dodjeljuje deblokiranu nit višeg prioriteta procesoru.

Napomena: Izvodljiva nit s najvišim prioritetom neće se uvijek izvoditi. Evo prioriteta Java Specifikacije jezika :

Svaka nit ima prioritet. Kada postoji konkurencija za obradu resursa, niti s višim prioritetom uglavnom se izvršavaju u prednost pred nitima s nižim prioritetom. Takva preferencija, međutim, nije jamstvo da će nit s najvišim prioritetom uvijek biti pokrenuta, a prioriteti niti ne mogu se koristiti za pouzdanu provedbu međusobnog izuzimanja.

To priznanje puno govori o primjeni JVM-ova zelenih niti. Ti JVM-ovi ne mogu dopustiti da niti blokiraju jer bi to povezalo JVM-ovu jedinu nit izvršenja. Stoga, kada nit mora blokirati, na primjer kada ta nit sporo čita podatke kako bi stigla iz datoteke, JVM može zaustaviti izvršavanje niti i upotrijebiti mehanizam ispitivanja da odredi kada podaci stignu. Iako nit ostaje zaustavljena, JVM-ov planer niti može planirati pokretanje niti nižeg prioriteta. Pretpostavimo da podaci stižu dok je nit nižeg prioriteta pokrenuta. Iako bi se nit s višim prioritetom trebala pokrenuti čim stignu podaci, to se događa sve dok JVM sljedeći put ne anketira operativni sustav i ne otkrije dolazak. Stoga se nit nižeg prioriteta izvodi iako bi se nit nižeg prioriteta trebala izvoditi.Trebate se brinuti zbog ove situacije samo kada vam je potrebno ponašanje s Java u stvarnom vremenu. Ali tada Java nije operativni sustav u stvarnom vremenu, pa zašto brinuti?

Da biste razumjeli koja zelena nit koja se može izvoditi postaje trenutno pokrenuta zelena nit, uzmite u obzir sljedeće. Pretpostavimo da se vaša aplikacija sastoji od tri niti: glavne niti koja pokreće main()metodu, niti izračuna i niti koja čita unos s tipkovnice. Kad nema tipkovnice, nit za čitanje se blokira. Pretpostavimo da nit čitanja ima najveći prioritet, a nit izračuna najniži prioritet. (Radi jednostavnosti, pretpostavimo i da nisu dostupne druge unutarnje JVM niti.) Slika 1 prikazuje izvršavanje ove tri niti.

U trenutku T0, glavna nit počinje raditi. U trenutku T1, glavna nit započinje nit izračuna. Budući da nit izračuna ima niži prioritet od glavne niti, nit izračuna računa čeka procesor. U trenutku T2, glavna nit započinje nit za čitanje. Budući da nit za čitanje ima veći prioritet od glavne niti, glavna nit čeka procesor dok se nit za čitanje izvodi. U vrijeme T3 nit za čitanje blokira i glavna nit radi. U trenutku T4 nit za čitanje se deblokira i pokreće; glavna nit čeka. Napokon, u vrijeme T5, nit za čitanje blokira i glavna nit radi. Ova izmjena u izvođenju između čitanja i glavne niti nastavlja se sve dok program radi. Nit za izračun nikada se ne pokreće jer ima najmanji prioritet i stoga gladuje za pažnju procesora,situacija poznata kaoprocesorsko gladovanje .

Ovaj scenarij možemo izmijeniti davanjem niti izračunavanju jednak prioritet glavnoj niti. Slika 2 prikazuje rezultat koji započinje s vremenom T2. (Prije T2, slika 2 identična je slici 1.)

At time T2, the reading thread runs while the main and calculation threads wait for the processor. At time T3, the reading thread blocks and the calculation thread runs, because the main thread ran just before the reading thread. At time T4, the reading thread unblocks and runs; the main and calculation threads wait. At time T5, the reading thread blocks and the main thread runs, because the calculation thread ran just before the reading thread. This alternation in execution between the main and calculation threads continues as long as the program runs and depends on the higher-priority thread running and blocking.

We must consider one last item in green thread scheduling. What happens when a lower-priority thread holds a lock that a higher-priority thread requires? The higher-priority thread blocks because it cannot get the lock, which implies that the higher-priority thread effectively has the same priority as the lower-priority thread. For example, a priority 6 thread attempts to acquire a lock that a priority 3 thread holds. Because the priority 6 thread must wait until it can acquire the lock, the priority 6 thread ends up with a 3 priority—a phenomenon known as priority inversion.

Inverzija prioriteta može uvelike odgoditi izvršavanje niti višeg prioriteta. Na primjer, pretpostavimo da imate tri niti s prioritetima 3, 4 i 9. nit prioriteta 3 je pokrenuta, a ostale niti su blokirane. Pretpostavimo da nit prioriteta 3 zgrabi bravu, a nit prioriteta 4 deblokira. Nit s prioritetom 4 postaje trenutno pokrenuta nit. Budući da nit prioriteta 9 zahtijeva zaključavanje, nastavlja čekati dok nit prioriteta 3 ne otpusti bravu. Međutim, nit 3 prioriteta ne može otpustiti bravu dok nit 4 prioriteta ne blokira ili prekine. Kao rezultat, nit prioriteta 9 odgađa svoje izvršavanje.