javabog.dk  |  << forrige  |  indhold  |  næste >>  |  programeksempler  |  om bogen

17 Flertrådet programmering

Indhold:

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).

17.1 Princip

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 interfacet Runnable, 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.

17.1.1 Eksempel

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 (denne undtagelse 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().

17.2 Ekstra eksempler

Se afsnit 9.4.2, Animationer med en separat tråd, for et eksempel på et grafisk program med flere tråde.

17.2.1 En flertrådet webserver

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.

17.2.2 Et flertrådet program med hoppende bolde

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) {};
    }
  }
}

17.3 Opgaver

  1. Udvid FlertraadetGrafik med andre figurer end bolde.

  2. 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.

17.4 Avanceret

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

17.4.1 Nedarvning fra Thread

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

17.4.2 Synkronisering

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

17.4.3 Synkronisering på objekter og semaforer

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

17.4.4 wait() og notify()

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

17.4.5 Prioritet

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

17.4.6 Opgaver

Dette afsnit er ikke omfattet af Åben Dokumentslicens.
Du skal købe bogen for at måtte læse dette afsnit.
Jeg erklærer, at jeg allerede har købt bogen
Jeg lover at anskaffe den i nær fremtid.

1Egentligt sker det med konstruktionen synchronized (this), som er beskrevet i næste afsnit.

javabog.dk  |  << forrige  |  indhold  |  næste >>  |  programeksempler  |  om bogen
http://javabog.dk/ - af Jacob Nordfalk.
Licens og kopiering under Åben Dokumentlicens (ÅDL) hvor intet andet er nævnt (82% af værket).

Ønsker du at se de sidste 18% af dette værk (199974 tegn) skal du købe bogen. Så får du pæne figurer og layout, stikordsregister og en trykt bog med i købet.