Programmering

Introduktion til Java-tråde

Denne artikel, en af ​​de første nogensinde udgivet af JavaWorld, beskriver hvordan tråde implementeres i Java-programmeringssproget, begyndende med en generel oversigt over tråde.

Kort sagt, a tråd er et programs gennemførelsesvej. De fleste programmer skrevet i dag kører som en enkelt tråd, hvilket forårsager problemer, når flere begivenheder eller handlinger skal forekomme på samme tid. Lad os sige, at et program for eksempel ikke er i stand til at tegne billeder, mens du læser tastetryk. Programmet skal have fuld opmærksomhed på tastaturindgangen, der mangler evnen til at håndtere mere end en begivenhed ad gangen. Den ideelle løsning på dette problem er problemfri udførelse af to eller flere sektioner af et program på samme tid. Tråde giver os mulighed for at gøre dette.

Lær om Java-tråde

Denne artikel er en del af JavaWorlds tekniske indholdsarkiv. Se følgende for at lære mere om Java-tråde og samtidighed:

Forståelse af Java-tråde (Java 101 serie, 2002):

  • Del 1: Introduktion af tråde og løbere
  • Del 2: Trådsynkronisering
  • Del 3: Trådplanlægning og vent / underret
  • Del 4: Trådgrupper og flygtighed

Relaterede artikler

  • Hyper-threaded Java: Brug af Java Concurrency API (2006)
  • Bedre skærme til multitrådede programmer (2007)
  • Forståelse af aktørens samtidighed, del 1 (2009)
  • Hængende tråddetektion og håndtering (2011)

Tjek også JavaWorld sitekort og søgemaskine.

Multitrådede applikationer leverer deres kraftige kraft ved at køre mange tråde samtidigt inden for et enkelt program. Fra et logisk synspunkt betyder multithreading, at flere linjer i et enkelt program kan udføres på samme tid, men det er ikke det samme som at starte et program to gange og sige, at der er flere linjer i et program, der udføres på samme tid tid. I dette tilfælde behandler operativsystemet programmerne som to separate og forskellige processer. Under Unix skaber forking af en proces en underordnet proces med et andet adresseområde til både kode og data. Imidlertid, gaffel() skaber en masse overhead til operativsystemet, hvilket gør det til en meget CPU-intensiv operation. Ved at starte en tråd i stedet oprettes en effektiv udførelsessti, mens den stadig deler det originale dataområde fra den overordnede. Ideen om at dele dataområdet er meget gavnlig, men bringer nogle områder af bekymring op, som vi vil diskutere senere.

Oprettelse af tråde

Java's skabere har nådigt designet to måder at oprette tråde på: implementering af en grænseflade og udvidelse af en klasse. At udvide en klasse er den måde, Java arver metoder og variabler fra en overordnet klasse. I dette tilfælde kan man kun udvide eller arve fra en eneforælderklasse. Denne begrænsning inden for Java kan overvindes ved at implementere grænseflader, som er den mest almindelige måde at oprette tråde på. (Bemærk, at arvingen kun tillader, at klassen køres som en tråd. Det er op til klassen at Start() udførelse osv.)

Grænseflader giver programmører mulighed for at lægge grundlaget for en klasse. De bruges til at designe kravene til et sæt klasser, der skal implementeres. Interfacet sætter alt op, og den eller de klasser, der implementerer grænsefladen, gør alt arbejdet. De forskellige klasser, der implementerer grænsefladen, skal følge de samme regler.

Der er et par forskelle mellem en klasse og en grænseflade. For det første kan en grænseflade kun indeholde abstrakte metoder og / eller statiske endelige variabler (konstanter). Klasser kan derimod implementere metoder og indeholde variabler, der ikke er konstanter. For det andet kan en grænseflade ikke implementere nogen metoder. En klasse, der implementerer en grænseflade, skal implementere alle metoder, der er defineret i grænsefladen. En grænseflade har evnen til at udvide fra andre grænseflader, og (i modsætning til klasser) kan den strække sig fra flere grænseflader. Desuden kan en grænseflade ikke instantieres med den nye operatør; for eksempel, Runnable a = ny Runnable (); er ikke tilladt.

Den første metode til at oprette en tråd er simpelthen at strække sig fra Tråd klasse. Gør dette kun, hvis den klasse, du har brug for at blive udført som en tråd, aldrig behøver at blive udvidet fra en anden klasse. Det Tråd klasse er defineret i pakken java.lang, som skal importeres, så vores klasser er opmærksomme på dens definition.

import java.lang. *; public class Counter udvider tråd {public void run () {....}}

Ovenstående eksempel opretter en ny klasse Tæller der udvider Tråd klasse og tilsidesætter Thread.run () metode til sin egen implementering. Det løb() metoden er, hvor alt arbejdet i Tæller klasse tråd er færdig. Den samme klasse kan oprettes ved at implementere Runnable:

import java.lang. *; offentlig klasse Tælleredskaber Runnable {Thread T; offentlig ugyldig kørsel () {....}}

Her det abstrakte løb() metoden er defineret i den Runnable-grænseflade og implementeres. Bemærk, at vi har en forekomst af Tråd klasse som en variabel af Tæller klasse. Den eneste forskel mellem de to metoder er, at ved at implementere Runnable er der større fleksibilitet i oprettelsen af ​​klassen Tæller. I ovenstående eksempel findes muligheden stadig for at udvide Tæller klasse, hvis det er nødvendigt. De fleste oprettede klasser, der skal køres som en tråd, implementerer Runnable, da de sandsynligvis udvider en anden funktionalitet fra en anden klasse.

Tro ikke, at den Runnable-grænseflade udfører noget rigtigt arbejde, når tråden udføres. Det er kun en klasse, der er oprettet for at give en idé om designet af Tråd klasse. Faktisk er det meget lille, der kun indeholder en abstrakt metode. Her er definitionen af ​​den Runnable-grænseflade direkte fra Java-kilden:

pakke java.lang; offentlig grænseflade Runnable {public abstract void run (); }

Det er alt hvad der er til den Runnable-grænseflade. En grænseflade giver kun et design, hvorpå klasser skal implementeres. I tilfældet med den Runnable-grænseflade tvinger den kun definitionen af løb() metode. Derfor udføres det meste af arbejdet i Tråd klasse. Et nærmere kig på et afsnit i definitionen af Tråd klasse vil give en idé om, hvad der virkelig foregår:

public class Tråd implementerer Runnable {... public void run () {if (target! = null) {target.run (); }} ...}

Fra ovenstående kodestykke er det tydeligt, at Thread-klassen også implementerer Runnable-grænsefladen. Tråd.løb() kontrollerer for at sikre, at målklassen (klassen, der skal køres som en tråd) ikke er lig med null, og derefter udfører løb() metode til målet. Når dette sker, løb() Metoden til målet kører som sin egen tråd.

Start og stop

Da de forskellige måder at oprette en forekomst af en tråd nu er tydelige på, vil vi diskutere implementeringen af ​​tråde, der begynder med de tilgængelige måder at starte og stoppe dem ved hjælp af en lille applet indeholdende en tråd for at illustrere mekanikken:

CounterThread-eksempel og kildekode

Ovenstående applet begynder at tælle fra 0, der viser dets output til både skærmen og konsollen. Et hurtigt blik kan give det indtryk, at programmet begynder at tælle og vise hvert nummer, men dette er ikke tilfældet. En nærmere undersøgelse af udførelsen af ​​denne applet vil afsløre dens sande identitet.

I dette tilfælde er Modtråd klasse blev tvunget til at implementere Runnable, da den udvidede klassen Applet. Som i alle applets er den i det() metoden bliver udført først. I i det(), initialiseres variabeltællingen til nul og en ny forekomst af Tråd klasse oprettes. Ved forbipasserende det her til Tråd konstruktør, vil den nye tråd vide, hvilket objekt der skal køres. I dette tilfælde det her er en henvisning til CounterThread. Når tråden er oprettet, skal den startes. Opkaldet til Start() vil kalde målene løb() metode, som er CounterThread.løb(). Opkaldet til Start() vender tilbage med det samme, og tråden begynder at udføres på samme tid. Bemærk, at løb() metoden er en uendelig løkke. Det er uendeligt, fordi når den løb() metode afsluttes, stopper tråden med at udføre. Det løb() metode øger variablen Count, sover i 10 millisekunder og sender en anmodning om at opdatere applets display.

Bemærk, at det er vigtigt at sove et sted i en tråd. Hvis ikke, bruger tråden al CPU-tid til processen og tillader ikke, at der udføres andre metoder såsom tråde. En anden måde at stoppe udførelsen af ​​en tråd på er at ringe til hold op() metode. I dette eksempel stopper tråden, når der trykkes på musen, mens markøren er i appleten. Afhængigt af computerens hastighed, som appleten kører på, vises ikke alle numre, fordi inkrementeringen sker uafhængigt af malingen af ​​appleten. Applet'en kan ikke opdateres ved hver anmodning, så OS vil sætte anmodningerne i kø, og successive opdateringsanmodninger vil blive tilfreds med en opdatering. Mens opdateringerne står i kø, øges optællingen stadig, men vises ikke.

Suspendering og genoptagelse

Når en tråd er stoppet, kan den ikke genstartes med Start() kommando, siden hold op() vil afslutte udførelsen af ​​en tråd. I stedet kan du pause udførelsen af ​​en tråd med søvn() metode. Tråden vil sove i et bestemt tidsrum og begynde derefter at udføre, når tidsgrænsen er nået. Men dette er ikke ideelt, hvis tråden skal startes, når en bestemt begivenhed opstår. I dette tilfælde er suspendere() metode tillader en tråd midlertidigt at ophøre med at udføre og Genoptag() metode gør det muligt for den suspenderede tråd at starte igen. Den følgende applet viser ovenstående eksempel modificeret til at suspendere og genoptage appleten.

offentlig klasse CounterThread2 udvider Applet implementerer Runnable {Thread t; int Count; boolsk ophængt offentlig boolsk mouseDown (begivenhed e, int x, int y) {hvis (suspenderet) t.resume (); ellers t.suspend (); suspenderet =! suspenderet; returner sandt; } ...}

CounterThread2 Eksempel og kildekode

For at holde styr på den aktuelle tilstand for appleten, den boolske variabel suspenderet anvendes. Det er vigtigt at skelne mellem de forskellige tilstande i en applet, fordi nogle metoder giver undtagelser, hvis de kaldes i forkert tilstand. For eksempel, hvis appleten er startet og stoppet, skal du udføre Start() metoden vil kaste en IllegalThreadStateException undtagelse.