Introduktion till Java-trådar

Den här artikeln, en av de första som någonsin publicerats av JavaWorld, beskriver hur trådar implementeras i Java-programmeringsspråket, med början med en allmän översikt över trådar.

Enkelt uttryckt är en tråd ett programs körväg. De flesta program som skrivs idag körs som en enda tråd, vilket orsakar problem när flera händelser eller åtgärder måste inträffa samtidigt. Låt oss säga att till exempel ett program inte kan rita bilder medan du läser tangenttryckningar. Programmet måste ägna sin fulla uppmärksamhet åt tangentbordets ingång som saknar förmågan att hantera mer än en händelse åt gången. Den perfekta lösningen på detta problem är det sömlösa utförandet av två eller flera sektioner av ett program samtidigt. Trådar gör att vi kan göra detta.

Lär dig mer om Java-trådar

Den här artikeln är en del av JavaWorlds tekniska innehållsarkiv. Se följande för att lära dig mer om Java-trådar och samtidighet:

Förstå Java-trådar ( Java 101- serien, 2002):

  • Del 1: Introduktion av trådar och körbarheter
  • Del 2: Trådsynkronisering
  • Del 3: Trådschemaläggning och vänta / meddela
  • Del 4: Trådgrupper och volatilitet

Relaterade artiklar

  • Hyper-threaded Java: Använda Java Concurrency API (2006)
  • Bättre bildskärmar för flertrådade program (2007)
  • Understanding Actor concurrency, Part 1 (2009)
  • Hängande tråddetektering och hantering (2011)

Kontrollera även JavaWorld- webbplatskartan och sökmotorn .

Multitrådade applikationer levererar sin kraft genom att köra många trådar samtidigt i ett enda program. Ur en logisk synpunkt betyder multithreading att flera rader i ett enda program kan köras samtidigt, men det är inte detsamma som att starta ett program två gånger och säga att det finns flera rader i ett program som körs samtidigt tid. I det här fallet behandlar operativsystemet programmen som två separata och distinkta processer. Under Unix skapar förfalskning av en process en underordnad process med ett annat adressutrymme för både kod och data. I alla fall,fork()skapar mycket overhead för operativsystemet, vilket gör det till en mycket CPU-intensiv operation. Genom att starta en tråd istället skapas en effektiv exekveringsväg samtidigt som det ursprungliga dataområdet delas från föräldern. Idén att dela dataområdet är mycket fördelaktig, men tar upp några problemområden som vi kommer att diskutera senare.

Skapa trådar

Java: s skapare har nådigt utformat två sätt att skapa trådar: implementera ett gränssnitt och utvidga en klass. Att utvidga en klass är det sätt som Java ärver metoder och variabler från en överordnad klass. I det här fallet kan man bara förlänga eller ärva från en ensamstående klass. Denna begränsning inom Java kan övervinnas genom att implementera gränssnitt, vilket är det vanligaste sättet att skapa trådar. (Observera att arvet endast gör att klassen kan köras som en tråd. Det är upp till klassen att start()utföras, etc.)

Gränssnitt ger programmerare möjlighet att lägga grunden för en klass. De används för att utforma kraven för en uppsättning klasser att implementera. Gränssnittet ställer upp allt och klassen eller klasserna som implementerar gränssnittet gör allt. De olika klasserna som implementerar gränssnittet måste följa samma regler.

Det finns några skillnader mellan en klass och ett gränssnitt. För det första kan ett gränssnitt endast innehålla abstrakta metoder och / eller statiska slutliga variabler (konstanter). Klasser kan å andra sidan implementera metoder och innehålla variabler som inte är konstanter. För det andra kan ett gränssnitt inte implementera några metoder. En klass som implementerar ett gränssnitt måste implementera alla metoder som definierats i det gränssnittet. Ett gränssnitt kan förlängas från andra gränssnitt, och (till skillnad från klasser) kan det sträcka sig från flera gränssnitt. Dessutom kan ett gränssnitt inte instansieras med den nya operatören; till exempel Runnable a=new Runnable();är inte tillåtet.

Den första metoden för att skapa en tråd är att helt enkelt sträcka sig från Threadklassen. Gör detta bara om klassen du behöver köras som en tråd inte behöver förlängas från en annan klass. Den Threadklassen definieras i paketet java.lang, som måste importeras så att våra klasser är medvetna om dess definition.

import java.lang.*; public class Counter extends Thread { public void run() { .... } }

Ovanstående exempel skapar en ny klass Countersom utökar Threadklassen och åsidosätter Thread.run()metoden för sin egen implementering. Den run()metoden är där allt arbete Counterklasstråden är klar. Samma klass kan skapas genom att implementera Runnable:

import java.lang.*; public class Counter implements Runnable { Thread T; public void run() { .... } }

Här run()definieras den abstrakta metoden i Runnable-gränssnittet och implementeras. Observera att vi har en instans av Threadklassen som en variabel av Counterklassen. Den enda skillnaden mellan de två metoderna är att genom att implementera Runnable finns det större flexibilitet i skapandet av klassen Counter. I exemplet ovan finns möjligheten fortfarande att utöka Counterklassen, om det behövs. Majoriteten av klasser som skapas som måste köras som en tråd kommer att implementera Runnable eftersom de förmodligen utvidgar någon annan funktionalitet från en annan klass.

Tror inte att det körbara gränssnittet gör något riktigt arbete när tråden körs. Det är bara en klass skapad för att ge en uppfattning om Threadklassens design . I själva verket är den väldigt liten med endast en abstrakt metod. Här är definitionen av det körbara gränssnittet direkt från Java-källan:

package java.lang; public interface Runnable { public abstract void run(); }

Det är allt som finns i Runnable-gränssnittet. Ett gränssnitt ger bara en design på vilken klasser ska implementeras. I fallet med Runnable-gränssnittet tvingar det endast definitionen av run()metoden. Därför görs det mesta av arbetet i Threadklassen. En närmare titt på ett avsnitt i definitionen av Threadklassen ger en uppfattning om vad som verkligen händer:

public class Thread implements Runnable { ... public void run() { if (target != null) { target.run(); } } ... }

Från ovanstående kodavsnitt är det uppenbart att Thread-klassen också implementerar det körbara gränssnittet. Thread. run()kontrollerar att målklassen (klassen som ska köras som en tråd) inte är lika med null och kör sedan run()metoden för målet. När detta händer run()kommer målmetoden att köras som sin egen tråd.

Start och stopp

Eftersom de olika sätten att skapa en instans av en tråd nu är uppenbara kommer vi att diskutera implementeringen av trådar som börjar med de tillgängliga sätten att starta och stoppa dem med en liten applet som innehåller en tråd för att illustrera mekaniken:

CounterThread Exempel och källkod

Ovanstående applet börjar räkna från 0 och visar dess utdata till både skärmen och konsolen. En snabb blick kan ge intrycket att programmet börjar räkna och visa varje nummer, men så är inte fallet. En närmare granskning av genomförandet av denna applet avslöjar dess verkliga identitet.

I det här fallet CounterThreadtvingades klassen att implementera Runnable eftersom det utökade klassen Applet. Som i alla appletter init()körs metoden först. I init(), initialiseras variabeln Antal till noll och en ny instans av Threadklassen skapas. Genom att gå vidare thistill Threadkonstruktören vet den nya tråden vilket objekt som ska köras. I detta fall thisär en hänvisning till CounterThread. När tråden har skapats måste den startas. Samtalet till start()kommer att ringa målmetoden run(), vilket är CounterThread. run(). Samtalet till start()återkommer direkt och tråden börjar köras samtidigt. Observera att run()metoden är en oändlig slinga. Det är oändligt eftersom en gångrun()metoden avslutas, slutar tråden att köras. Den run()metod kommer öka variabeln Count, sömn för 10 millisekunder och skicka en begäran om att uppdatera applet display.

Note that it is important to sleep somewhere in a thread. If not, the thread will consume all CPU time for the process and will not allow any other methods such as threads to be executed. Another way to cease the execution of a thread is to call the stop() method. In this example, the thread stops when the mouse is pressed while the cursor is in the applet. Depending on the speed of the computer the applet runs on, not every number will be displayed, because the incrementing is done independent of the painting of the applet. The applet can not be refreshed at every request, so the OS will queue the requests and successive refresh requests will be satisfied with one refresh. While the refreshes are queuing up, the Count is still being incremented but not displayed.

Suspending and resuming

Once a thread is stopped, it cannot be restarted with the start() command, since stop() will terminate the execution of a thread. Instead you can pause the execution of a thread with the sleep() method. The thread will sleep for a certain period of time and then begin executing when the time limit is reached. But, this is not ideal if the thread needs to be started when a certain event occurs. In this case, the suspend() method allows a thread to temporarily cease executing and the resume() method allows the suspended thread to start again. The following applet shows the above example modified to suspend and resume the applet.

public class CounterThread2 extends Applet implements Runnable { Thread t; int Count; boolean suspended; public boolean mouseDown(Event e,int x, int y) { if(suspended) t.resume(); else t.suspend(); suspended = !suspended; return true; } ... }

CounterThread2 Example and Source code

För att hålla reda på det aktuella läget för appleten används den booleska variabeln suspended. Att skilja mellan de olika tillstånden i en applet är viktigt eftersom vissa metoder ger undantag om de kallas i fel tillstånd. Till exempel, om appleten har startats och stoppats start()kommer ett IllegalThreadStateExceptionundantag att köra metoden .