Programmering

Java 101: Forståelse af Java-tråde, del 1: Introduktion til tråde og kører

Denne artikel er den første i en firedel Java 101 serie, der udforsker Java-tråde. Selvom du måske tror, ​​at trådning i Java ville være udfordrende at forstå, har jeg til hensigt at vise dig, at tråde er lette at forstå. I denne artikel introducerer jeg dig til Java-tråde og -kørsler. I efterfølgende artikler undersøger vi synkronisering (via låse), synkroniseringsproblemer (såsom deadlock), vent / underretningsmekanismen, planlægning (med og uden prioritet), trådafbrydelse, timere, volatilitet, trådgrupper og tråd lokale variabler .

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

Hvad er en tråd?

Begrebsmæssigt er forestillingen om en tråd er ikke svært at forstå: det er en uafhængig udførelsesvej gennem programkode. Når der udføres flere tråde, adskiller en tråds sti gennem den samme kode normalt sig fra de andre. Antag for eksempel, at en tråd udfører bytekodeækvivalenten for en if-else-sætning hvis del, mens en anden tråd udfører bytekodeækvivalenten for andet en del. Hvordan holder JVM styr på hver tråds udførelse? JVM giver hver tråd sin egen metode-opkaldsstak. Ud over at spore den aktuelle bytekodeinstruktion sporer metoden opkaldsstakken lokale variabler, parametre, som JVM overfører til en metode, og metodens returværdi.

Når flere tråde udfører byte-kode instruktionssekvenser i det samme program, er denne handling kendt som multithreading. Multithreading gavner et program på forskellige måder:

  • Multithreaded GUI (grafisk brugergrænseflade) -baserede programmer forbliver lydhøre over for brugerne, mens de udfører andre opgaver, såsom ompaginering eller udskrivning af et dokument.
  • Trådede programmer slutter typisk hurtigere end deres ikke-trådede kolleger. Dette gælder især for tråde, der kører på en multiprocessormaskine, hvor hver tråd har sin egen processor.

Java udfører multithreading gennem sin java.lang.Tråd klasse. Hver Tråd objekt beskriver en enkelt udførelsestråd. Denne henrettelse finder sted i Tråd's løb() metode. Fordi standard løb() metode gør ingenting, skal du underklasse Tråd og tilsidesætte løb() at udføre nyttigt arbejde. For en smag af tråde og multithreading i sammenhæng med Tråd, undersøg Listing 1:

Liste 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 udvider tråd {public void run () {for (int count = 1, row = 1; row <20; row ++, count ++) {for (int i = 0; i <count; i ++) System.out. Print ('*'); System.out.print ('\ n'); }}}

Liste 1 viser kildekode til et program, der består af klasser TrådDemo og MyThread. Klasse TrådDemo driver applikationen ved at oprette en MyThread objekt, start en tråd, der associeres med det objekt, og udfør en eller anden kode for at udskrive en tabel med firkanter. I modsætning, MyThread tilsidesætter Tråd's løb() metode til at udskrive (i standardudgangsstrømmen) en retvinkletrekant sammensat af stjerne tegn.

Trådplanlægning og JVM

De fleste (hvis ikke alle) JVM-implementeringer bruger den underliggende platforms threading-muligheder. Fordi disse funktioner er platformsspecifikke, kan rækkefølgen af ​​dine multitrådede programmers output afvige fra rækkefølgen af ​​andres output. Denne forskel skyldes planlægning, et emne jeg udforsker senere i denne serie.

Når du skriver java ThreadDemo for at køre applikationen opretter JVM en starttråd til udførelse, som udfører hoved () metode. Ved at udføre mt.start ();, fortæller starttråden JVM om at oprette en anden udførelsestråd, der udfører bytekodeinstruktionerne, der omfatter MyThread objekt løb() metode. Når Start() metode vender tilbage, starttråden udfører dens til loop for at udskrive en tabel med firkanter, mens den nye tråd udfører løb() metode til at udskrive retvinkletrekanten.

Hvordan ser output ud? Løb TrådDemo at finde ud af. Du vil bemærke, at hver tråds output har en tendens til at krydse med den andres output. Det resulterer, fordi begge tråde sender deres output til den samme standardudgangsstrøm.

Trådklassen

For at blive dygtige til at skrive flertrådet kode skal du først forstå de forskellige metoder, der udgør Tråd klasse. Dette afsnit udforsker mange af disse metoder. Specifikt lærer du om metoder til at starte tråde, navngive tråde, sætte tråde i dvale, bestemme om en tråd er i live, forbinde en tråd til en anden tråd og tælle alle aktive tråde i den aktuelle tråds trådgruppe og undergrupper. Jeg diskuterer også Tråd's fejlfindingshjælpemidler og brugertråde versus dæmontråde.

Jeg præsenterer resten af Tråd's metoder i efterfølgende artikler, med undtagelse af Suns forældede metoder.

Forældede metoder

Sol har afskaffet en række forskellige Tråd metoder, såsom suspendere() og Genoptag(), fordi de kan låse dine programmer eller beskadige genstande. Som et resultat skal du ikke kalde dem i din kode. Se SDK-dokumentationen for løsning af disse metoder. Jeg dækker ikke forældede metoder i denne serie.

Konstruktion af tråde

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

  • Tråd(), som skaber en Tråd objekt med et standardnavn
  • Tråd (strengnavn), som skaber en Tråd objekt med et navn, som navn argument specificerer

De næste enkleste konstruktører er Tråd (kørbart mål) og Tråd (kørbart mål, strengnavn). Bortset fra Kan køres parametre, er disse konstruktører identiske med de førnævnte konstruktører. Forskellen: Kan køres parametre identificerer objekter uden for Tråd der giver løb() metoder. (Du lærer om Kan køres senere i denne artikel.) De sidste fire konstruktører ligner Tråd (strengnavn), Tråd (kørbart mål)og Tråd (kørbart mål, strengnavn); dog inkluderer de endelige konstruktører også en Trådgruppe argument til organisatoriske formål.

En af de sidste fire konstruktører, Tråd (ThreadGroup-gruppe, kørbart mål, strengnavn, lang stackSize), er interessant, fordi det lader dig specificere den ønskede størrelse af trådens metode-opkaldsstak. At være i stand til at specificere, at størrelsen viser sig nyttig i programmer med metoder, der bruger rekursion - en udførelsesteknik, hvorved en metode gentagne gange kalder sig selv - til elegant løsning af visse problemer. Ved eksplicit at indstille stakkens størrelse kan du nogle gange forhindre det StackOverflowErrors. Dog kan en for stor størrelse resultere i OutOfMemoryErrors. Sun betragter også metodeopkaldsstakens størrelse som platformafhængig. Afhængigt af platformen kan størrelsen på metodeopkaldsstakken muligvis ændre sig. Tænk derfor grundigt over konsekvenserne af dit program, før du skriver kode, der ringer Tråd (ThreadGroup-gruppe, kørbart mål, strengnavn, lang stackSize).

Start dine køretøjer

Tråde ligner køretøjer: de flytter programmer fra start til slut. Tråd og Tråd underklasseobjekter er ikke tråde. I stedet beskriver de en tråds attributter, såsom dens navn, og indeholder kode (via en løb() metode), som tråden udfører. Når tiden er inde til at udføre en ny tråd løb(), en anden tråd kalder Trådeller dens underklasseobjekter Start() metode. For eksempel, for at starte en anden tråd, er programmets starttråd - som udføres hoved ()—Opkald Start(). Som svar fungerer JVM's trådhåndteringskode med platformen for at sikre, at tråden initialiseres korrekt og kalder en Trådeller dens underklasseobjekter løb() metode.

Enkelt gang Start() udfylder, flere tråde udføres. Fordi vi har tendens til at tænke lineært, har vi ofte svært ved at forstå samtidig (samtidig) aktivitet, der opstår, når to eller flere tråde kører. Derfor skal du undersøge et diagram, der viser, hvor en tråd udfører (dens position) versus tid. Figuren nedenfor viser et sådant diagram.

Diagrammet viser flere vigtige tidsperioder:

  • Starttrådens initialisering
  • Det øjeblik, tråden begynder at udføre hoved ()
  • I det øjeblik denne tråd begynder at udføre Start()
  • Øjeblikket Start() opretter en ny tråd og vender tilbage til hoved ()
  • Den nye tråd initialiseres
  • I det øjeblik den nye tråd begynder at udføre løb()
  • De forskellige øjeblikke, hver tråd afsluttes

Bemærk, at den nye tråd initialiseres, dens udførelse af løb(), og dens afslutning sker samtidigt med starttrådens udførelse. Bemærk også, at efter en trådopkald Start(), efterfølgende opkald til denne metode før løb() metode afslutter årsag Start() at smide en java.lang.IllegalThreadStateException objekt.

Hvad er i et navn?

Under en debugging-session viser det sig nyttigt at skelne en tråd fra en anden på en brugervenlig måde. For at skelne mellem tråde knytter Java et navn til en tråd. Navnet er som standard Tråd, et bindestregtegn og et nulbaseret heltal. Du kan acceptere Java's standard trådnavne, eller du kan vælge dine egne. For at imødekomme brugerdefinerede navne Tråd giver konstruktører, der tager navn argumenter og a setName (strengnavn) metode. Tråd giver også en getName () metode, der returnerer det aktuelle navn. Liste 2 viser, hvordan man opretter et brugerdefineret navn via Tråd (strengnavn) konstruktør og hente det aktuelle navn i løb() metode ved at ringe getName ():

Notering 2. NameThatThread.java

// NameThatThread.java klasse NameThatThread {public static void main (String [] args) {MyThread mt; hvis (args.længde == 0) mt = ny MyThread (); ellers mt = ny MyThread (args [0]); mt.start (); }} klasse MyThread udvider tråd {MyThread () {// Compileren opretter bytekodeækvivalenten super (); } MyThread (String name) {super (name); // Pass navn til tråd superklasse} offentlig ugyldig kørsel () {System.out.println ("Mit navn er:" + getName ()); }}

Du kan videregive et valgfrit navnargument til MyThread på kommandolinjen. For eksempel, java NameThatTråd X etablerer x som trådens navn. Hvis du ikke angiver et navn, ser du følgende output:

Mit navn er: Tråd-1

Hvis du foretrækker det, kan du ændre super (navn); ring i MyThread (strengnavn) konstruktør til et opkald til setName (strengnavn)-som i sætnavn (navn);. Denne sidstnævnte metodeopkald opnår det samme mål - at etablere trådens navn - som super (navn);. Jeg lader det være som en øvelse for dig.

Navngivning af hoved

Java tildeler navnet vigtigste til tråden, der kører hoved () metode, starttråden. Du ser typisk dette navn i Undtagelse i tråden "main" besked om, at JVM's standard undtagelsesbehandler udskrives, når starttråden kaster et undtagelsesobjekt.

At sove eller ikke sove

Senere i denne kolonne vil jeg introducere dig til animation- gentagne gange tegner på den ene overflade billeder, der adskiller sig lidt fra hinanden for at opnå en bevægelsesillusion. For at udføre animation skal en tråd pause under visningen af ​​to på hinanden følgende billeder. Ringer Tråder statisk søvn (lange millis) metode tvinger en tråd til pause for millis millisekunder. En anden tråd kunne muligvis afbryde den sovende tråd. Hvis det sker, vågner den sovende tråd og kaster en Afbrudt undtagelse objekt fra søvn (lange millis) metode. Som et resultat kode, der ringer søvn (lange millis) skal vises inden for en prøve blok - eller kodens metode skal indeholde Afbrudt undtagelse i dets kaster klausul.

At demonstrere søvn (lange millis), Jeg har skrevet en CalcPI1 Ansøgning. Denne applikation starter en ny tråd, der bruger en matematisk algoritme til at beregne værdien af ​​den matematiske konstant pi. Mens den nye tråd beregnes, stopper starttråden i 10 millisekunder ved at ringe søvn (lange millis). Efter starttråden er vågnet, udskriver den pi-værdien, som den nye tråd lagrer i variabel pi. Notering 3 gaver CalcPI1kildekode:

Notering 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 udvider tråd {boolsk negativ = sand; dobbelt pi; // Initialiseres til 0,0, som standard offentlig ugyldig kørsel () {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; System.out.println ("Færdig beregning af PI"); }}

Hvis du kører dette program, vil du se output svarende til (men sandsynligvis ikke identisk) til følgende:

pi = -0.2146197014017295 Færdig beregning af PI