Programmering

Java 101: Forståelse af Java-tråde, del 3: Trådplanlægning og vent / underret

Denne måned fortsætter jeg min firedelte introduktion til Java-tråde ved at fokusere på trådplanlægning, vent / underretningsmekanismen og trådafbrydelse. Du undersøger, hvordan enten en JVM eller en trådløs planlægger til operativsystemet vælger den næste tråd til udførelse. Som du vil opdage, er prioritet vigtig for en trådplanlægnings valg. Du vil undersøge, hvordan en tråd venter, indtil den modtager underretning fra en anden tråd, før den fortsætter udførelsen, og lære at bruge vent / underretningsmekanismen til at koordinere udførelsen af ​​to tråde i et forhold mellem producent og forbruger. Endelig lærer du, hvordan du tidligt vækker enten en sovende eller en ventende tråd til trådafslutning eller andre opgaver. Jeg vil også lære dig, hvordan en tråd, der hverken sover eller venter, registrerer en afbrydelsesanmodning fra en anden tråd.

Bemærk, at denne artikel (en del af JavaWorld-arkiverne) blev opdateret med nye kodelister og kildekode, der kan downloades, i maj 2013.

Forståelse af Java-tråde - læs hele serien

  • Del 1: Introduktion af tråde og løbere
  • Del 2: Synkronisering
  • Del 3: Trådplanlægning, vent / underret og trådafbrydelse
  • Del 4: Trådgrupper, volatilitet, tråd-lokale variabler, timere og tråddød

Trådplanlægning

I en idealiseret verden ville alle programtråde have deres egne processorer, som de kunne køre på. Indtil det tidspunkt kommer, hvor computere har tusinder eller millioner af processorer, skal tråde ofte dele en eller flere processorer. Enten JVM eller den underliggende platforms operativsystem afkoder, hvordan man deler processorressourcen mellem tråde - en opgave kendt som trådplanlægning. Den del af JVM eller operativsystem, der udfører trådplanlægning, er en trådplanlægning.

Bemærk: For at forenkle min trådplanlægningsdiskussion fokuserer jeg på trådplanlægning i sammenhæng med en enkelt processor. Du kan ekstrapolere denne diskussion til flere processorer; Jeg overlader den opgave til dig.

Husk to vigtige punkter om trådplanlægning:

  1. Java tvinger ikke en VM til at planlægge tråde på en bestemt måde eller indeholde en trådplanlægning. Det indebærer platformafhængig trådplanlægning. Derfor skal du være forsigtig, når du skriver et Java-program, hvis adfærd afhænger af, hvordan tråde er planlagt og skal fungere konsekvent på tværs af forskellige platforme.
  2. Heldigvis, når du skriver Java-programmer, skal du tænke på, hvordan Java kun planlægger tråde, når mindst en af ​​dit programs tråde i høj grad bruger processoren i lange perioder, og mellemliggende resultater af den tråds udførelse viser sig at være vigtige. For eksempel indeholder en applet en tråd, der dynamisk skaber et billede. Med jævne mellemrum vil du have, at malerietråden tegner billedets aktuelle indhold, så brugeren kan se, hvordan billedet skrider frem. Overvej trådplanlægning for at sikre, at beregningstråden ikke monopoliserer processoren.

Undersøg et program, der opretter to processorintensive tråde:

Notering 1. SchedDemo.java

// SchedDemo.java klasse SchedDemo {public static void main (String [] args) {new CalcThread ("CalcThread A"). Start (); ny CalcThread ("CalcThread B"). start (); }} klasse CalcThread udvider tråd {CalcThread (strengnavn) {// Videregiv navn til trådlag. super (navn); } dobbelt calcPI () {boolsk negativ = sand; dobbelt pi = 0,0; for (int i = 3; i <100000; i + = 2) {hvis (negativ) pi - = (1.0 / i); ellers pi + = (1,0 / i); negativ =! negativ; } pi + = 1,0; pi * = 4,0; returner pi; } offentlig ugyldig kørsel () {for (int i = 0; i <5; i ++) System.out.println (getName () + ":" + calcPI ()); }}

SchedDemo opretter to tråde, der hver beregner værdien af ​​pi (fem gange) og udskriver hvert resultat. Afhængigt af hvordan din JVM-implementering planlægger tråde, kan du muligvis se output, der ligner følgende:

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

I henhold til ovenstående output deler trådplanlæggeren processoren mellem begge tråde. Du kan dog se output svarende til dette:

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

Ovenstående output viser trådplanlæggeren, der favoriserer en tråd frem for en anden. De to output ovenfor illustrerer to generelle kategorier af trådplanlægere: grøn og native. Jeg vil undersøge deres adfærdsmæssige forskelle i kommende sektioner. Mens jeg diskuterer hver kategori, henviser jeg til trådtilstande, hvoraf der er fire:

  1. Oprindelig tilstand: Et program har oprettet en tråds trådobjekt, men tråden eksisterer endnu ikke, fordi trådobjektets Start() metoden er endnu ikke blevet kaldt.
  2. Kørbar tilstand: Dette er en tråds standardtilstand. Efter opkaldet til Start() afslutter, bliver en tråd kørbar, uanset om den tråd kører eller ikke, dvs. ved hjælp af processoren. Selvom mange tråde muligvis kan køres, kører kun en i øjeblikket. Trådplanlægning bestemmer, hvilken tråd der kan køres, der skal tildeles til processoren.
  3. Blokeret tilstand: Når en tråd udfører søvn(), vente(), eller tilslutte() metoder, når en tråd forsøger at læse data, der endnu ikke er tilgængelig fra et netværk, og når en tråd venter på at erhverve en lås, er denne tråd i blokeret tilstand: den kører ikke eller er i stand til at køre. (Du kan sandsynligvis tænke på andre tidspunkter, hvor en tråd ville vente på, at der skulle ske noget.) Når en blokeret tråd låses op, flyttes den tråd til den tilstand, der kan køres.
  4. Afslutningstilstand: Når udførelsen efterlader en tråd løb() metode, er denne tråd i den afsluttende tilstand. Med andre ord ophører tråden med at eksistere.

Hvordan vælger trådplanlæggeren, hvilken tråd der kan køres? Jeg begynder at besvare dette spørgsmål, mens jeg diskuterer planlægning af grøn tråd. Jeg afslutter svaret, mens jeg diskuterer indfødt trådplanlægning.

Grøn trådplanlægning

Ikke alle operativsystemer, det gamle Microsoft Windows 3.1-perating-system, understøtter for eksempel tråde. For sådanne systemer kan Sun Microsystems designe en JVM, der deler sin eneste udførelsestråd i flere tråde. JVM (ikke den underliggende platforms operativsystem) leverer trådlogikken og indeholder trådplanlæggeren. JVM-tråde er grønne tråde, eller brugertråde.

En JVMs trådplanlægning planlægger grønne tråde i henhold til prioritet—En tråds relative betydning, som du udtrykker som et heltal fra et veldefineret interval af værdier. Typisk vælger en JVMs trådplanlægning den højeste prioritets tråd og tillader den tråd at køre, indtil den enten slutter eller blokerer. På det tidspunkt vælger trådplanlæggeren en tråd med den næsthøjeste prioritet. Den tråd løber (normalt), indtil den slutter eller blokerer. Hvis en tråd med højere prioritet afblokeres, mens en tråd kører (måske trådens højere prioritet er udløbet), er trådplanlæggeren forhindrer, eller afbryder tråden med lavere prioritet og tildeler den ikke-blokerede tråd med højere prioritet til processoren.

Bemærk: En kørbar tråd med højeste prioritet kører ikke altid. Her er den Java Language Specification 's tage prioritet:

Hver tråd har en prioritet. Når der er konkurrence om behandlingsressourcer, udføres tråde med højere prioritet generelt frem for tråde med lavere prioritet. En sådan præference er dog ikke en garanti for, at den højeste prioritets tråd altid vil køre, og trådprioriteter kan ikke bruges til pålideligt at implementere gensidig udelukkelse.

Denne optagelse siger meget om implementeringen af ​​grønne tråd-JVM'er. Disse JVM'er har ikke råd til at lade tråde blokere, fordi det ville binde JVMs eneste udførelsestråd. Derfor, når en tråd skal blokere, f.eks. Når den tråd læser data langsomt for at komme fra en fil, kan JVM muligvis stoppe trådens udførelse og bruge en afstemningsmekanisme til at bestemme, hvornår data ankommer. Mens tråden forbliver stoppet, planlægger JVM's trådplanlægger muligvis en tråd med lavere prioritet, der skal køres. Antag, at data ankommer, mens tråden med lavere prioritet kører. Selvom tråden med højere prioritet skal køre, så snart data ankommer, sker det ikke, før JVM næste afstemning af operativsystemet og opdager ankomsten. Derfor kører tråden med lavere prioritet, selvom tråden med højere prioritet skal køre. Du skal kun bekymre dig om denne situation, når du har brug for realtidsadfærd fra Java. Men så er Java ikke et operativsystem i realtid, så hvorfor bekymre sig?

For at forstå, hvilken grøn, der kan køres, der bliver den grønne tråd, der kører i øjeblikket, skal du overveje følgende. Antag, at din ansøgning består af tre tråde: hovedtråden, der kører hoved () metode, en beregningstråd og en tråd, der læser tastaturinput. Når der ikke er tastaturindgang, blokerer læsningstråden. Antag, at læsetråden har den højeste prioritet, og beregningstråden har den laveste prioritet. (For enkelheds skyld antager du også, at ingen andre interne JVM-tråde er tilgængelige.) Figur 1 illustrerer udførelsen af ​​disse tre tråde.

På tidspunktet T0 begynder hovedtråden at køre. På tidspunktet T1 starter hovedtråden beregningstråden. Da beregningstråden har en lavere prioritet end hovedtråden, venter beregningstråden på processoren. På tidspunktet T2 starter hovedtråden læsetråden. Da læsetråden har en højere prioritet end hovedtråden, venter hovedtråden på processoren, mens læsetråden kører. På tidspunktet T3 blokerer læsetråden og hovedtråden. På tidspunktet T4 låses læsetråden op og kører; hovedtråden venter. Endelig, på tidspunktet T5, blokerer læsningstråden og hovedtråden. Denne veksling i udførelse mellem læsning og hovedtråde fortsætter, så længe programmet kører. Beregningstråden kører aldrig, fordi den har den laveste prioritet og dermed sulter efter processorens opmærksomhed, en situation kendt som processor sult.

Vi kan ændre dette scenarie ved at give beregningstråden samme prioritet som hovedtråden. Figur 2 viser resultatet, begyndende med tid T2. (Før T2 er figur 2 identisk med figur 1.)

På tidspunktet T2 kører læsetråden, mens hoved- og beregningstrådene venter på processoren. På tidspunktet T3 blokerer læsningstråden og beregningstråden, fordi hovedtråden løb lige før læsetråden. På tidspunktet T4 låses læsetråden op og kører; hoved- og beregningstrådene venter. På tidspunktet T5 blokerer læsningstråden og hovedtråden, fordi beregningstråden kørte lige før læsetråden. Denne veksling i udførelse mellem hoved- og beregningstrådene fortsætter, så længe programmet kører og afhænger af tråden med højere prioritet, der kører og blokerer.

Vi skal overveje et sidste element i planlægning af grøn tråd. Hvad sker der, når en tråd med lavere prioritet holder en lås, som en tråd med højere prioritet kræver? Tråd med højere prioritet blokerer, fordi den ikke kan få låsen, hvilket betyder, at tråden med højere prioritet effektivt har samme prioritet som tråden med lavere prioritet. For eksempel forsøger en prioritet 6 tråd at erhverve en lås, som en prioritet 3 tråd holder. Fordi prioritet 6-tråden skal vente, indtil den kan erhverve låsen, ender prioritet 6-tråden med en 3-prioritet - et fænomen kendt som prioritetsinversion.

Prioritetsinversion kan i høj grad forsinke udførelsen af ​​en tråd med højere prioritet. Antag for eksempel, at du har tre tråde med prioriteter på 3, 4 og 9. Prioritet 3-tråden kører, og de andre tråde er blokeret. Antag, at prioritet 3-tråden griber en lås, og prioritet 4 tråd fjernes. Prioritet 4 tråd bliver den aktuelt kørende tråd. Da prioritet 9-tråden kræver lås, fortsætter den med at vente, indtil tråden med prioritet 3 frigiver låsen. Prioritet 3-tråden kan imidlertid ikke frigøre låsen, før prioritet 4-gevind blokerer eller afsluttes. Som et resultat forsinker prioritet 9-tråden dens udførelse.