Indhold:
Forstå tråde
Eksempel på en flertrådet webserver
Kapitlet forudsættes ikke i resten af bogen.
Forudsætter kapitel 12, Interfaces (kapitel 16, Netværkskommunikation og kapitel 9, Grafiske programmer bruges i nogle eksempler).
Når man kommer ud over den grundlæggende programmering, ønsker man tit at lave programmer, som udfører flere opgaver løbende. Det kan f.eks. være et tekstbehandlingsprogram, hvor man ønsker at kunne gemme eller udskrive i baggrunden, mens brugeren redigerer videre, eller man ønsker løbende stavekontrol samtidig med, at brugeren skriver. Skrivningen må ikke blive forsinket af, at programmet sideløbende forbereder en udskrift eller kontrollerer stavningen. Disse delprocesser (kaldet tråde) har lav prioritet i forhold til at håndtere brugerens input og vise det på skærmen, og selvom de midlertidigt skulle gå i stå, skal de andre dele af programmet køre videre.
Et flertrådet program er et program med flere tilsyneladende samtidige programudførelsespunkter (i virkeligheden vil CPU'en skifte meget hurtigt rundt mellem punkterne og udføre lidt af hver).
Det er ret let at programmere flere tråde i Java. Man opretter en ny tråd med et objekt i konstruktøren:
Thread tråd; tråd = new Thread(obj);
Objektet obj skal implementere Runnable-interfacet, f.eks.:
public class UdførbartObjekt implements Runnable
{
public void run() // kræves af Runnable
{
// her starter den nye tråd med at køre
// ...
}
}
Tråden er nu klar til at udføre run()-metoden på objektet, men den er ikke startet endnu. Man starter den ved at kalde start()-metoden på tråd-objektet:
tråd.start(); // her kører den oprindelige tråd videre, mens den nye tråd kører i obj.run() // ...
Derefter vil der være to programudførelsespunkter: Et vil være i koden efter kaldet til start(), og den anden vil være ved begyndelsen af run()-metoden i objektet.
En
tråd oprettes med et objekt, der implementerer
Runnable-interfacet.
Når start() kaldes på tråden,
vil den begynde at udføre run() på objektet.
Herunder definerer vi klassen SnakkesagligPerson.Objekter af typen SnakkesaligPerson kan køre i en tråd (implements Runnable). I konstruktøren angives navnet på personen, og hvor lang tid der går, mellem, at personen taler.
Når run() udføres, skriver den personens navn + bla bla bla ud så ofte som angivet.
public class SnakkesagligPerson implements Runnable { private String navn; private int ventetid; public SnakkesagligPerson(String n, int t) { navn = n; ventetid = t; } public void run() { for (int i=0; i<5; i++) { System.out.println(navn+": bla bla bla "+i); try { Thread.sleep(ventetid); } catch (Exception e) {} // vent lidt } } }
Da Thread.sleep() kan kaste undtagelser af typen InterruptedException, er vi nødt til at indkapsle koden i en try-catch-blok (disse undtagelser forekommer aldrig i praksis).
Vi kan nu oprette en snakkesalig person, der siger noget hvert sekund:
SnakkesagligPerson p = new SnakkesagligPerson("Brian",1000);
... og en tråd, der er klar til at udføre p.run() og lade personen snakke:
Thread t = new Thread(p);
... og til sidst startes tråden, så personen snakker:
t.start();
Her ses et samlet eksempel, der opretter 3 snakkesalige personer, Jacob, Troels og Henrik, og lader dem snakke i munden på hinanden (i hver sin tråd).
public class BenytSnakkesagligePersoner { public static void main(String[] arg) { SnakkesagligPerson p = new SnakkesagligPerson("Jacob",150); Thread t = new Thread(p); // Ny tråd, klar til at udføre p.run() t.start(); // .. Nu starter personen med at snakke... p = new SnakkesagligPerson("Troels",400); t = new Thread(p); t.start(); // Det kan også gøres meget kompakt: new Thread(new SnakkesagligPerson("Henrik",200)).start(); } }
Jacob: bla bla bla 0 Troels: bla bla bla 0 Henrik: bla bla bla 0 Jacob: bla bla bla 1 Henrik: bla bla bla 1 Jacob: bla bla bla 2 Troels: bla bla bla 1 Henrik: bla bla bla 2 Jacob: bla bla bla 3 Henrik: bla bla bla 3 Jacob: bla bla bla 4 Troels: bla bla bla 2 Henrik: bla bla bla 4 Troels: bla bla bla 3 Troels: bla bla bla 4
Bemærk, at udførelsen af main(), der faktisk sker i en fjerde tråd, afsluttes med det samme, men at programmet kører videre, indtil de tre tråde er færdige med deres opgaver; Java fortsætter med at udføre et program, så længe der er tråde, der stadig er aktive, dvs. ikke har returneret fra run().
Se afsnit 9.4.2, Animationer med en separat tråd, for et xxx
Herunder har vi lavet en flertrådet webserver (sammenlign med Hjemmesidevaert i kapitel 16). For at gøre det nemmere at se, hvad der foregår, lader vi hver anmodning vente i 10 sekunder, før den afslutter.
import java.io.*; import java.net.*; import java.util.*; public class Anmodning implements Runnable { private Socket forbindelse; Anmodning(Socket forbindelse) { this.forbindelse = forbindelse; } public void run() { try { PrintWriter ud = new PrintWriter(forbindelse.getOutputStream()); BufferedReader ind = new BufferedReader( new InputStreamReader(forbindelse.getInputStream())); String anmodning = ind.readLine(); System.out.println("start "+new Date()+" "+anmodning); ud.println("HTTP/0.9 200 OK"); ud.println(); ud.println("<html><head><title>Svar</title></head>"); ud.println("<body><h1>Svar</h1>"); ud.println("Tænker over "+anmodning+"<br>"); for (int i=0; i<100; i++) { ud.print(".<br>"); ud.flush(); Thread.sleep(100); } ud.println("Nu har jeg tænkt færdig!</body></html>"); ud.flush(); forbindelse.close(); System.out.println("slut "+new Date()+" "+anmodning); } catch (Exception e) { e.printStackTrace(); } } }
Når der kommer en anmodning, oprettes et Anmodning-objekt, der snakker med klienten og behandler forespørgslen, og en ny tråd knyttes til anmodningen.
import java.io.*; import java.net.*; public class FlertraadetHjemmesidevaert { public static void main(String[] arg) { try { ServerSocket værtssokkel = new ServerSocket(8001); while (true) { Socket forbindelse = værtssokkel.accept(); Anmodning a = new Anmodning(forbindelse); new Thread(a).start(); } } catch (Exception e) { e.printStackTrace(); } } }
start Tue Nov 28 15:37:31 GMT+01:00 2000 GET /xx.html HTTP/1.0 start Tue Nov 28 15:37:38 GMT+01:00 2000 GET /yy.html HTTP/1.0 start Tue Nov 28 15:37:42 GMT+01:00 2000 GET /zz.html HTTP/1.0 slut Tue Nov 28 15:37:42 GMT+01:00 2000 GET /xx.html HTTP/1.0 slut Tue Nov 28 15:37:49 GMT+01:00 2000 GET /yy.html HTTP/1.0 start Tue Nov 28 15:37:50 GMT+01:00 2000 GET /qq.html HTTP/1.0 slut Tue Nov 28 15:37:53 GMT+01:00 2000 GET /zz.html HTTP/1.0 slut Tue Nov 28 15:38:01 GMT+01:00 2000 GET /qq.html HTTP/1.0
Programmet er afprøvet ved at åbne adressen http://localhost:8001/xx.html hhv. yy, zz og qq.html i en netlæser. Man ser, at anmodningerne xx, yy og zz behandles samtidigt.
Lad os lave et program med nogle bolde, der hopper rundt. Hver bold kører i sin egen tråd.
Når en bold oprettes, får den i konstruktøren overført start-koordinater og et Graphics-objekt, som den husker. Den opretter og starter en tråd, som kører run()-metoden.
Vi kan så oprette et vindue, få dets Graphics-objekt og oprette nogle bolde med det, som vist herunder:
import java.awt.*; public class FlertraadetGrafik { public static void main(String[] arg) { Frame f = new Frame(); f.setSize(400,150); f.setBackground(Color.white); f.setVisible(true); Graphics g = f.getGraphics(); new Bold(g, 0, 0); new Bold(g, 50,10); new Bold(g,100,50); new Bold(g,150,90); } }
import java.awt.*; public class Bold implements Runnable { double x, y, fartx, farty; Graphics g; public Bold(Graphics g1, int x1, int y1) { g = g1; x = x1; y = y1; fartx = Math.random(); farty = Math.random(); Thread t = new Thread(this); t.start(); } public void run() { for (int tid=0; tid<5000; tid++) { // Tegn bolden over med hvid på den gamle position g.setColor(Color.white); g.drawOval((int) x, (int) y, 50, 50); // Opdater positionen med farten x = x + fartx; y = y + farty; // Tegn bolden med sort på den nye position g.setColor(Color.black); g.drawOval((int) x, (int) y, 50, 50); // ændr boldens hastighed lidt nedad farty = farty + 0.1; // Hvis bolden er uden for det tilladte område skal den // rettes hen mod området if (x < 0) fartx = Math.abs(fartx); if (x > 400) fartx = -Math.abs(fartx); if (y < 0) farty = Math.abs(farty); if (y > 100) farty = -Math.abs(farty); // Vent lidt try { Thread.sleep(10); } catch (Exception e) {}; } } }
Udvid FlertraadetGrafik med andre figurer end bolde.
Skriv et program, der udregner primtal (se kapitel 2 for inspiration). Samtidig med, at programmet regner, skal det kunne kommunikere med brugeren og give ham mulighed for at afslutte programmet og udskrive de primtal, der er fundet indtil nu.
1Det er den faktisk ikke. Af effektivitetshensyn er det den underliggende datastrøm, der er synkroniseret på.