Programmering

Java 101: Forstå Java-tråder, del 3: Trådplanlegging og vent / varsle

Denne måneden fortsetter jeg min firedelte introduksjon til Java-tråder ved å fokusere på trådplanlegging, vent / varslingsmekanismen og trådavbrudd. Du vil undersøke hvordan enten en JVM eller et trådløs planleggingsprogram for operativsystemet velger neste tråd for utføring. Som du vil oppdage, er prioritet viktig for en trådplanlegger. Du vil undersøke hvordan en tråd venter til den mottar varsel fra en annen tråd før den fortsetter utførelsen, og lære hvordan du bruker vent / varslingsmekanismen for å koordinere utførelsen av to tråder i et forhold mellom produsent og forbruker. Til slutt vil du lære å vekke enten en sovende eller en ventende tråd for trådavslutning eller andre oppgaver for tidlig. Jeg vil også lære deg hvordan en tråd som ikke sover eller venter, oppdager en forespørsel om avbrudd fra en annen tråd.

Merk at denne artikkelen (en del av JavaWorld-arkivene) ble oppdatert med nye kodelister og nedlastbar kildekode i mai 2013.

Forstå Java-tråder - les hele serien

  • Del 1: Introduksjon av tråder og løpere
  • Del 2: Synkronisering
  • Del 3: Trådplanlegging, vent / varsling og trådavbrudd
  • Del 4: Trådgrupper, volatilitet, tråd-lokale variabler, tidtakere og tråddød

Trådplanlegging

I en idealisert verden ville alle programtråder ha sine egne prosessorer å kjøre på. Inntil tiden kommer når datamaskiner har tusenvis eller millioner av prosessorer, må tråder ofte dele en eller flere prosessorer. Enten JVM eller den underliggende plattformens operativsystem dekrypterer hvordan du skal dele prosessorressursen mellom tråder - en oppgave kjent som trådplanlegging. Den delen av JVM eller operativsystem som utfører trådplanlegging er en trådplanlegger.

Merk: For å forenkle trådplanleggingsdiskusjonen, fokuserer jeg på trådplanlegging i sammenheng med en enkelt prosessor. Du kan ekstrapolere denne diskusjonen til flere prosessorer; Jeg overlater den oppgaven til deg.

Husk to viktige punkter om trådplanlegging:

  1. Java tvinger ikke en virtuell maskin til å planlegge tråder på en bestemt måte eller inneholde en trådplanlegger. Det innebærer plattformavhengig trådplanlegging. Derfor må du være forsiktig når du skriver et Java-program hvis oppførsel avhenger av hvordan tråder er planlagt og må fungere konsekvent på tvers av forskjellige plattformer.
  2. Heldigvis, når du skriver Java-programmer, må du tenke på hvordan Java bare planlegger tråder når minst en av programmets tråder bruker prosessoren tungt i lange perioder, og mellomresultater av utførelsen av den tråden viser seg å være viktig. For eksempel inneholder en applet en tråd som dynamisk skaper et bilde. Med jevne mellomrom vil du at malingstråden skal tegne det aktuelle innholdet i bildet slik at brukeren kan se hvordan bildet utvikler seg. For å sikre at beregningstråden ikke monopoliserer prosessoren, bør du vurdere trådplanlegging.

Undersøk et program som lager to prosessorintensive tråder:

Oppføring 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 utvider tråd {CalcThread (strengnavn) {// Gi navn til trådlag. super (navn); } dobbel calcPI () {boolsk negativ = sann; dobbelt pi = 0,0; for (int i = 3; i <100000; i + = 2) {hvis (negativ) pi - = (1.0 / i); annet pi + = (1,0 / i); negativ =! negativ; } pi + = 1,0; pi * = 4,0; retur pi; } public void run () {for (int i = 0; i <5; i ++) System.out.println (getName () + ":" + calcPI ()); }}

SchedDemo oppretter to tråder som hver beregner verdien av pi (fem ganger) og skriver ut hvert resultat. Avhengig av hvordan JVM-implementeringen din planlegger tråder, kan du se utdata som ligner på følgende:

CalcThread A: 3,1415726535897894 B CalcThread: 3,1415726535897894 CalcThread A: 3,1415726535897894 CalcThread A: 3,1415726535897894 B CalcThread: 3,1415726535897894 CalcThread A: 3,1415726535897894 CalcThread A: 3,1415726535897894 B CalcThread: 3,1415726535897894 B CalcThread: 3,1415726535897894 CalcThread B: 3,1415726535897894

I henhold til ovennevnte utdata, deler trådplanleggeren prosessoren mellom begge trådene. Du kan imidlertid se utdata som ligner på dette:

CalcThread A: 3,1415726535897894 CalcThread A: 3,1415726535897894 CalcThread A: 3,1415726535897894 CalcThread A: 3,1415726535897894 CalcThread A: 3,1415726535897894 B CalcThread: 3,1415726535897894 B CalcThread: 3,1415726535897894 B CalcThread: 3,1415726535897894 B CalcThread: 3,1415726535897894 CalcThread B: 3,1415726535897894

Ovenstående utgang viser trådplanleggeren som favoriserer en tråd fremfor en annen. De to utgangene ovenfor illustrerer to generelle kategorier av trådplanleggere: grønn og innfødt. Jeg vil utforske deres atferdsmessige forskjeller i kommende seksjoner. Mens jeg diskuterer hver kategori, viser jeg til trådtilstander, hvorav det er fire:

  1. Opprinnelige tilstand: Et program har opprettet trådens trådobjekt, men tråden eksisterer ennå ikke fordi trådobjektet er start() metoden har ennå ikke blitt kalt.
  2. Kjørbar tilstand: Dette er trådens standardtilstand. Etter samtalen til start() fullfører, blir en tråd kjørbar uansett om den tråden kjører eller ikke, det vil si ved hjelp av prosessoren. Selv om mange tråder kan kjøres, kjører bare en for øyeblikket. Trådplanleggere bestemmer hvilken kjørbar tråd som skal tildeles prosessoren.
  3. Blokkert tilstand: Når en tråd utfører søvn(), vente(), eller bli med() metoder, når en tråd prøver å lese data som ennå ikke er tilgjengelig fra et nettverk, og når en tråd venter på å skaffe seg en lås, er den tråden i blokkert tilstand: den kjører ikke eller er i stand til å kjøre. (Du kan sannsynligvis tenke på andre ganger når en tråd ville vente på at noe skulle skje.) Når en blokkert tråd låser opp, beveger den tråden seg til den kjørbare tilstanden.
  4. Avslutningstilstand: Når utførelsen etterlater en tråd løpe() metoden, er den tråden i avsluttende tilstand. Tråden slutter med andre ord å eksistere.

Hvordan velger trådplanleggeren hvilken kjørbar tråd som skal kjøres? Jeg begynner å svare på det spørsmålet mens jeg diskuterer planlegging av grønn tråd. Jeg avslutter svaret mens jeg diskuterer innfødt trådplanlegging.

Grønn trådplanlegging

Ikke alle operativsystemer, for eksempel det gamle Microsoft Windows 3.1-perateringssystemet, støtter tråder. For slike systemer kan Sun Microsystems designe en JVM som deler sin eneste gjennomføringstråd i flere tråder. JVM (ikke operativsystemet til den underliggende plattformen) leverer trådlogikken og inneholder trådplanleggeren. JVM-tråder er grønne tråder, eller brukertråder.

En JVMs trådplanlegger planlegger grønne tråder i henhold til prioritet—En tråds relative betydning, som du uttrykker som et heltall fra et veldefinert verdiområde. En JVMs trådplanlegger velger vanligvis tråden med høyest prioritet og lar den tråden kjøre til den enten avsluttes eller blokkeres. På den tiden velger trådplanleggeren en tråd med neste høyeste prioritet. Den tråden går (vanligvis) til den slutter eller blokkeres. Hvis en tråd med høyere prioritet blokkeres (mens tråden går) (kanskje tråden med høyere prioritet har gått ut), vil trådplanleggeren preempt, eller avbryter tråden med lavere prioritet og tilordner den ikke-blokkerte tråden med høyere prioritet til prosessoren.

Merk: En kjørbar tråd med høyest prioritet vil ikke alltid kjøre. Her er Java Language Specification 's prioriterer:

Hver tråd har en prioritet. Når det er konkurranse om behandling av ressurser, blir tråder med høyere prioritet vanligvis utført i stedet for tråder med lavere prioritet. En slik preferanse er imidlertid ikke en garanti for at tråden med høyest prioritet alltid vil kjøre, og trådprioriteringer kan ikke brukes til å pålitelig implementere gjensidig ekskludering.

Den innrømmelsen sier mye om implementeringen av grønne tråd-JVM-er. Disse JVM-ene har ikke råd til å la tråder blokkere fordi det ville binde JVMs eneste gjennomføringstråd. Derfor, når en tråd må blokkere, for eksempel når den tråden leser data sakte for å komme fra en fil, kan JVM stoppe trådens kjøring og bruke en avstemningsmekanisme for å bestemme når data kommer. Mens tråden forblir stoppet, kan JVMs trådplanlegger planlegge en tråd med lavere prioritet som skal kjøres. Anta at data kommer mens tråden med lavere prioritet kjører. Selv om tråden med høyere prioritet skal kjøre så snart data kommer, skjer det ikke før JVM neste avstemning om operativsystemet og oppdager ankomsten. Derfor kjører tråden med lavere prioritet selv om tråden med høyere prioritet skal kjøres. Du trenger bare å bekymre deg for denne situasjonen når du trenger sanntidsadferd fra Java. Men da er ikke Java et sanntidsoperativsystem, så hvorfor bekymre deg?

For å forstå hvilken løpbar grønn tråd som blir den nåværende grønne tråden, bør du vurdere følgende. Anta at søknaden din består av tre tråder: hovedtråden som kjører hoved() metode, en beregningstråd og en tråd som leser tastaturinngang. Når det ikke er tastaturinngang, blokkeres lesetråden. Anta at lesetråden har høyest prioritet og beregningstråden har lavest prioritet. (For enkelhets skyld antar du også at ingen andre interne JVM-tråder er tilgjengelige.) Figur 1 illustrerer utførelsen av disse tre trådene.

På tidspunktet T0 begynner hovedtråden å løpe. På tidspunktet T1 starter hovedtråden beregningstråden. Fordi beregningstråden har lavere prioritet enn hovedtråden, venter beregningstråden på prosessoren. På tidspunktet T2 starter hovedtråden lesetråden. Fordi lesetråden har høyere prioritet enn hovedtråden, venter hovedtråden på prosessoren mens lesetråden går. Ved tid T3 blokkeres lesetråden og hovedtråden går. På tidspunktet T4 blokkeres lesingstråden og går; hovedtråden venter. Til slutt, på tid T5, blokkerer lesetråden og hovedtråden. Denne vekslingen i utførelse mellom lese- og hovedtrådene fortsetter så lenge programmet kjører. Beregningstråden kjører aldri fordi den har lavest prioritet og dermed sulter etter prosessorens oppmerksomhet, en situasjon kjent som prosessor sult.

Vi kan endre dette scenariet ved å gi beregningstråden samme prioritet som hovedtråden. Figur 2 viser resultatet, begynnende med tid T2. (Før T2 er figur 2 identisk med figur 1.)

På tidspunktet T2 går lesetråden mens hoved- og beregningstrådene venter på prosessoren. På tidspunktet T3 blokkeres lesetråden og beregningstråden, fordi hovedtråden gikk rett før lesetråden. På tidspunktet T4 blokkeres lesingstråden og går; hoved- og beregningstrådene venter. På tid T5 blokkeres lesetråden og hovedtråden, fordi beregningstråden gikk rett før lesetråden. Denne vekslingen i utførelse mellom hoved- og beregningstrådene fortsetter så lenge programmet kjører, og avhenger av at tråden som har høyere prioritet kjører og blokkerer.

Vi må vurdere et siste element i planlegging av grønn tråd. Hva skjer når en tråd med lavere prioritet holder en lås som en tråd med høyere prioritet krever? Tråden med høyere prioritet blokkerer fordi den ikke kan få låsen, noe som innebærer at tråden med høyere prioritet effektivt har samme prioritet som tråden med lavere prioritet. For eksempel forsøker en prioritet 6-tråd å skaffe seg en lås som en prioritet 3-tråd holder. Fordi prioritet 6-tråden må vente til den kan hente låsen, ender prioritet 6-tråden med 3 prioritet - et fenomen kjent som prioritert inversjon.

Prioritetsinversjon kan i stor grad forsinke utførelsen av en tråd med høyere prioritet. Anta for eksempel at du har tre tråder med prioriteringene 3, 4 og 9. Prioritet 3-tråden kjører, og de andre trådene er blokkert. Anta at prioritet 3-tråden griper en lås, og at prioritet 4 tråd fjernes. Prioritet 4-tråden blir den gjeldende tråden. Fordi prioritet 9-tråden krever lås, fortsetter den å vente til prioritet 3-tråden frigjør låsen. Prioritet 3-tråden kan imidlertid ikke frigjøre låsen før prioritet 4-tråden blokkerer eller avsluttes. Som et resultat forsinker prioritet 9-tråden kjøringen.

$config[zx-auto] not found$config[zx-overlay] not found