Programmering

Java 101: Forstå Java-tråder, del 1: Introduksjon av tråder og kjører

Denne artikkelen er den første i en firedel Java 101 serie som utforsker Java-tråder. Selv om du kanskje tror det er vanskelig å forstå trådene i Java, har jeg tenkt å vise deg at trådene er enkle å forstå. I denne artikkelen introduserer jeg deg for Java-tråder og runnables. I påfølgende artikler vil vi utforske synkronisering (via låser), synkroniseringsproblemer (som låsing), vent / varslingsmekanismen, planlegging (med og uten prioritet), trådavbrudd, tidtakere, volatilitet, trådgrupper og trådvariabler .

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 og vent / varsle
  • Del 4: Trådgrupper og volatilitet

Hva er en tråd?

Konseptuelt er forestillingen om en tråd er ikke vanskelig å forstå: det er en uavhengig gjennomføringsvei gjennom programkode. Når flere tråder kjøres, skiller vanligvis en tråds vei gjennom den samme koden seg fra de andre. Anta for eksempel at en tråd utfører bytekodeekvivalenten til en if-else-setning hvis del, mens en annen tråd utfører bytekodeekvivalenten til ellers del. Hvordan holder JVM oversikt over utførelsen av hver tråd? JVM gir hver tråd sin egen metode-samtalestabel. I tillegg til å spore den nåværende bytekodeinstruksjonen, sporer metoden-samtalestakken lokale variabler, parametere JVM overfører til en metode og metodens returverdi.

Når flere tråder utfører sekvenser for byte-kodeinstruksjoner i samme program, er denne handlingen kjent som multithreading. Multithreading fordeler et program på forskjellige måter:

  • Flertrådede GUI (grafisk brukergrensesnitt) -baserte programmer forblir lydhør overfor brukere mens de utfører andre oppgaver, for eksempel repaginering eller utskrift av et dokument.
  • Gjengede programmer avsluttes vanligvis raskere enn deres ikke-gjengede kolleger. Dette gjelder spesielt tråder som kjører på en flerprosessormaskin, hvor hver tråd har sin egen prosessor.

Java utfører multitrading gjennom sin java.lang.Tråd klasse. Hver Tråd objektet beskriver en enkelt gjennomføringstråd. Den henrettelsen skjer i Tråds løpe() metode. Fordi standard løpe() metoden gjør ingenting, må du underklasse Tråd og overstyre løpe() å utføre nyttig arbeid. For en smak av tråder og multitråding i sammenheng med Tråd, undersøk Listing 1:

Oppføring 1. ThreadDemo.java

// ThreadDemo.java klasse ThreadDemo {public static void main (String [] args) {MyThread mt = new MyThread (); mt.start (); for (int i = 0; i <50; i ++) System.out.println ("i =" + i + ", i * i =" + i * i); }} klasse MyThread utvider tråd {public void run () {for (int count = 1, row = 1; row <20; row ++, count ++) {for (int i = 0; i <count; i ++) System.out. skrive ut ('*'); System.out.print ('\ n'); }}}

Oppføring 1 presenterer kildekoden til et program som består av klasser Tråddemo og MyThread. Klasse Tråddemo driver applikasjonen ved å opprette en MyThread objekt, starter en tråd som assosieres med det objektet, og utfører kode for å skrive ut en tabell med firkanter. I motsetning, MyThread overstyrer Tråds løpe() metode for å skrive ut (i standard utgangsstrøm) en rettvinkletrekant sammensatt av stjerne tegn.

Trådplanlegging og JVM

De fleste (om ikke alle) JVM-implementeringer bruker den underliggende plattformens threading-muligheter. Fordi disse funksjonene er plattformspesifikke, kan rekkefølgen på flertrådede programmers produksjon variere fra rekkefølgen til andres utdata. Denne forskjellen skyldes planlegging, et emne jeg utforsker senere i denne serien.

Når du skriver java ThreadDemo for å kjøre applikasjonen, oppretter JVM en starttråd for kjøring, som kjører hoved() metode. Ved å utføre mt.start ();, forteller starttråden til JVM å lage en annen utføringstråd som utfører bytekodeinstruksjonene som inneholder MyThread objektets løpe() metode. Når start() metoden returnerer, starttråden utfører sin til loop for å skrive ut en tabell med firkanter, mens den nye tråden utfører løpe() metode for å skrive ut vinkletrekanten.

Hvordan ser utgangen ut? Løpe Tråddemo å finne ut. Du vil legge merke til at hver tråds utgang har en tendens til å krysse den andres utdata. Det resulterer fordi begge trådene sender utdataene til samme standard utgangsstrøm.

Trådklassen

For å bli dyktig til å skrive flertrådet kode, må du først forstå de forskjellige metodene som utgjør Tråd klasse. Denne delen utforsker mange av disse metodene. Spesielt lærer du om metoder for å starte tråder, navngi tråder, sette tråder i dvale, bestemme om en tråd er i live, knytte en tråd til en annen tråd, og oppregne alle aktive tråder i trådens trådgruppe og undergrupper. Jeg diskuterer også Tråd's feilsøkingshjelpemidler og brukertråder versus daemon-tråder.

Jeg presenterer resten av Trådsine metoder i påfølgende artikler, med unntak av Suns utdaterte metoder.

Utfasede metoder

Sol har avskrevet en rekke Tråd metoder, for eksempel utsette() og gjenoppta(), fordi de kan låse programmene dine eller skade gjenstander. Som et resultat, bør du ikke ringe dem i koden din. Se SDK-dokumentasjonen for å finne løsninger på disse metodene. Jeg dekker ikke utdaterte metoder i denne serien.

Konstruerer tråder

Tråd har åtte konstruktører. De enkleste er:

  • Tråd(), som skaper en Tråd objekt med standardnavn
  • Tråd (strengnavn), som skaper en Tråd objekt med et navn som Navn argument spesifiserer

De neste enkleste konstruktørene er Tråd (kjørbart mål) og Tråd (kjørbart mål, strengnavn). Bortsett fra Kjørbar parametere, er disse konstruktørene identiske med de nevnte konstruktørene. Forskjellen: Kjørbar parametere identifiserer objekter utenfor Tråd som gir løpe() metoder. (Du lærer om Kjørbar senere i denne artikkelen.) De siste fire konstruktørene ligner Tråd (strengnavn), Tråd (kjørbart mål), og Tråd (kjørbart mål, strengnavn); de endelige konstruktørene inkluderer imidlertid også en Trådgruppe argument for organisatoriske formål.

En av de fire siste konstruktørene, Tråd (ThreadGroup-gruppe, kjørbart mål, strengnavn, lang stackSize), er interessant fordi den lar deg spesifisere ønsket størrelse på trådens metode-samtalestabel. Å kunne spesifisere at størrelsen viser seg nyttig i programmer med metoder som benytter rekursjon - en utførelsesteknikk der en metode gjentatte ganger kaller seg selv - for elegant å løse visse problemer. Ved å angi stabelstørrelsen eksplisitt kan du noen ganger forhindre StackOverflowErrors. Imidlertid kan for stor størrelse resultere i OutOfMemoryErrors. Sun anser også størrelsen på metodekallstakken som plattformavhengig. Avhengig av plattform, kan størrelsen på metoden og samtalestakken endres. Tenk derfor nøye gjennom konsekvensene til programmet ditt før du skriver kode som ringer Tråd (ThreadGroup-gruppe, kjørbart mål, strengnavn, lang stackSize).

Start kjøretøyene dine

Tråder ligner kjøretøy: de flytter programmer fra start til slutt. Tråd og Tråd underklasseobjekter er ikke tråder. I stedet beskriver de en tråds attributter, for eksempel navnet, og inneholder kode (via en løpe() metode) som tråden utfører. Når tiden er inne for en ny tråd å utføre løpe(), kaller en annen tråd Trådeller dens underklasseobjekt start() metode. For eksempel, for å starte en annen tråd, er programmets starttråd — som kjøres hoved()—Samtaler start(). Som svar fungerer JVMs trådhåndteringskode med plattformen for å sikre at tråden initialiseres riktig og kaller a Trådeller dens underklasseobjekt løpe() metode.

En gang start() fullfører, utfører flere tråder. Fordi vi har en tendens til å tenke lineært, synes vi ofte det er vanskelig å forstå samtidig (samtidig) aktivitet som oppstår når to eller flere tråder kjører. Derfor bør du undersøke et diagram som viser hvor en tråd utfører (sin posisjon) kontra tid. Figuren nedenfor presenterer et slikt diagram.

Diagrammet viser flere viktige tidsperioder:

  • Starttråden initialiseres
  • Øyeblikket den tråden begynner å utføres hoved()
  • Øyeblikket den tråden begynner å utføres start()
  • Øyeblikket start() oppretter en ny tråd og går tilbake til hoved()
  • Initialiseringen av den nye tråden
  • Øyeblikket den nye tråden begynner å utføres løpe()
  • De forskjellige øyeblikkene hver tråd avslutter

Merk at initialisering av den nye tråden, gjennomføring av løpe(), og avslutningen skjer samtidig med starttrådens utførelse. Legg også merke til at etter at en tråd ringer start(), påfølgende samtaler til den metoden før løpe() metoden går ut årsaken start() å kaste en java.lang.IllegalThreadStateException gjenstand.

Hva er i et navn?

Under en feilsøkingsøkt er det nyttig å skille en tråd fra en annen på en brukervennlig måte. For å skille mellom tråder, knytter Java et navn til en tråd. Dette navnet er som standard Tråd, et bindestrektegn og et nullbasert heltall. Du kan godta Java's standard trådnavn, eller du kan velge dine egne. For å imøtekomme tilpassede navn, Tråd gir konstruktører som tar Navn argumenter og a setName (strengnavn) metode. Tråd gir også en getName () metode som returnerer gjeldende navn. Oppføring 2 viser hvordan du oppretter et tilpasset navn via Tråd (strengnavn) og hente gjeldende navn i løpe() metode ved å ringe getName ():

Oppføring 2. NameThatThread.java

// NameThatThread.java klasse NameThatThread {public static void main (String [] args) {MyThread mt; hvis (args.lengde == 0) mt = ny MyThread (); annet mt = ny MyThread (args [0]); mt.start (); }} klasse MyThread utvider tråd {MyThread () {// Kompilatoren lager bytekodeekvivalenten super (); } MyThread (strengnavn) {super (navn); // Pass name to Thread superclass} public void run () {System.out.println ("Mitt navn er:" + getName ()); }}

Du kan sende et valgfritt navnargument til MyThread på kommandolinjen. For eksempel, java NameThatTråd X etablerer X som trådens navn. Hvis du ikke spesifiserer et navn, ser du følgende utdata:

Mitt navn er: Tråd-1

Hvis du foretrekker det, kan du endre super (navn); ring inn MyThread (strengnavn) konstruktør til en samtale til setName (strengnavn)-som i setName (navn);. Den sistnevnte metodekallen oppnår samme mål - å etablere trådens navn - som super (navn);. Jeg lar det være som en øvelse for deg.

Navngi hoved

Java tildeler navnet hoved- til tråden som kjører hoved() metode, starttråden. Du ser vanligvis det navnet i Unntak i tråd "hoved" melding om at JVMs standard unntaksbehandler skrives ut når starttråden kaster et unntaksobjekt.

Å sove eller ikke sove

Senere i denne kolonnen vil jeg introdusere deg for animasjon- tegner gjentatte ganger på den ene overflaten bilder som er litt forskjellige fra hverandre for å oppnå en bevegelsesillusjon. For å oppnå animasjon må en tråd stoppe mens den viser to påfølgende bilder. Ringer Tråder statisk sove (lange millis) metoden tvinger en tråd til pause for millis millisekunder. En annen tråd kan muligens avbryte sovetråden. Hvis det skjer, våkner sovetråden og kaster en InterruptedException objekt fra søvn (lange millis) metode. Som et resultat, kode som ringer sove (lange millis) må vises i en prøve blokk - eller kodens metode må inneholde InterruptedException i sin kaster klausul.

Å demonstrere sove (lange millis), Jeg har skrevet en CalcPI1 applikasjon. Det programmet starter en ny tråd som bruker en matematisk algoritme for å beregne verdien av den matematiske konstanten pi. Mens den nye tråden beregnes, stopper starttråden i 10 millisekunder ved å ringe søvn (lange millis). Etter at starttråden våkner, skrives den ut pi-verdien, som den nye tråden lagrer i variabel pi. Oppføring 3 gaver CalcPI1kildekode:

Oppføring 3. CalcPI1.java

// CalcPI1.java klasse CalcPI1 {public static void main (String [] args) {MyThread mt = new MyThread (); mt.start (); prøv {Thread.sleep (10); // Sov i 10 millisekunder} fangst (InterruptedException e) {} System.out.println ("pi =" + mt.pi); }} klasse MyThread utvider tråd {boolsk negativ = sann; dobbelt pi; // Initialiserer til 0,0, som standard offentlig ugyldig kjøring () {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; System.out.println ("Ferdig beregning av PI"); }}

Hvis du kjører dette programmet, vil du se utdata som ligner (men sannsynligvis ikke identisk) med følgende:

pi = -0.2146197014017295 Ferdig beregning av PI
$config[zx-auto] not found$config[zx-overlay] not found