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åd
s 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åd
s 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åd
sine 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 enTråd
objekt med standardnavnTråd (strengnavn)
, som skaper enTråd
objekt med et navn somNavn
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 StackOverflowError
s. Imidlertid kan for stor størrelse resultere i OutOfMemoryError
s. 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åd
eller 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åd
eller 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 tilhoved()
- 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åd
er 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 CalcPI1
kildekode:
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