Nada

Föreläsning 5: Trådar.

Trådar

Kuggfråga: Vilket program körs när man skriver java Pnyxtr extraord? Svar: Programmet JVM, alltså javas virtuella maskin, startas av kommandot java. JVM laddar in klassen Pnyxtr.class och gör anropet main(extraord). När denna exekveringstråd når den avslutande högermåsen i main avslutar JVM körningen, såvida inte någon annan exekveringstråd ännu pågår. All javagrafik körs i en särskild tråd, så om man öppnat något fönster avslutas inte programmet när main är slut.

Anropet System.exit(0) avbryter JVM och därmed avslutas körningen tvärt och alla trådar dör. Det går lika bra med System.exit(17), talet läggs bara i operativsystemets statusvariabel. Unixkommandot echo $status visar variabelvärdet på skärmen och konventionen är att värden större än noll betyder felavbrott.

Det finns tre tillfällen när man måste skapa flera trådar i sitt program.

En tråd är ett objekt av klassen Thread eller en klass som extends Thread. Tråden börjar exekvera när dess start-metod anropas, alltså tråd.start(); och den kod som då körs är den som finns i metoden public void run(). Själva Thread-klassen har en tom run-metod, så det vill till att ens egen trådklass ersätter den med en intressantare run-metod. Så här kan det se ut.
import java.awt.*                               ;  
import javax.swing.*                            ; 
class Nada extends JFrame                       {
  int tid=10                                    ;
  Box box = Box.createVerticalBox()             ;
  JLabel fråga = new JLabel("Vad betyder NADA?");
  JTextField svar = new JTextField(25)          ;
  JLabel tidkvar = new JLabel(tid+" s kvar")    ;
  String rätt="Numerisk analys och datalogi"    ;
//-------------inre klassen Tråd-----------------
  class Tråd extends Thread                     {
    public void run()                           {
      while(tid-->0)                            {
	try                                     {
          sleep(1000)                           ;} 
        catch(Exception e)                      {
          System.out.println(e)                 ;}
	tidkvar.setText(tid+" s kvar")          ;}
      tidkvar.setText("Tiden ute!!!")           ;
      if(svar.getText().equals(rätt)) 
        fråga.setText("Rätt svar!!")            ;
      else fråga.setText("Fel, Nada="+rätt)     ;}}
//------------konstruktorn för Nada-------------
  public Nada()                                 {
    super("Snabb på tangenterna")               ;
    box.add(fråga)                              ;
    box.add(svar)                               ;
    box.add(tidkvar)                            ;
    box.setBackground(Color.yellow)             ;
    getContentPane().add(box)                   ;
    pack()                                      ;
    show()                                      ;
    new Tråd().start()                          ;}
//------------main för Nada---------------------
  public static void main(String[] args)        {
    new Nada()                                  ;}}
Klassen Tråd finns till endast för att få in rätt run-metod i Thread-objektet. Egentligen skulle man vilja skicka över metoden som en parameter, alltså new Thread(run) men så fär man inte göra i Java. Det finns ett dock ett knep för att skicka över metoder som parametrar och det är designmönstret Command.

Designmönstret Command

Det finns en konstruktor new Thread(obj) som tar ett objekt som parameter, nämligen det objekt som innehåller den önskade run-metoden. För att kompilatorn ska gå med på detta används följande enkla gränssnitt.
 
 interface Runnable {    
   public void run();}
I Nada-exemplet skulle man slippa den inre trådklassen om man använt satsen
 
      new Thread(this).start();
som talar om att det är run-metoden i Nadaklassen som tråden ska köra. Men då måste klassen ha deklarerats så här:
 
  class Nada extends JFrame implements Runnable

Tricket har upphöjts till designmönstret Command.

 
   _________              _______
  |Commander|1          */Runnable\        En Commander kan 
  |_________|------------\________/        ta hand om flera 
                             ^             Command-objekt och 
                             |             exekvera deras run-
                                           metoder vid lämpliga
                          ___|___          tillfällen.
                         |Command|
                         |-------|
                         | run() |
                         |_______|

Trådsäkerhet

Om man har flera parallella trådar kan det bli konstiga fel vid sällsynta tillfällen. Det beror på att bara en tråd exekveras i taget och kan tillfälligt avbrytas när som helst för att en annan tråd ska få köra ett tag. Om båda trådarna mekar med samma variabler är inte programmet trådsäkert.

Typfallet är om två bankomater tar ut pengar från samma konto samtidigt. Bankomaterna är klienter till bankens server och som vi sett får varje klient en egen tråd i serverprogrammet. Anta att den ena tråden gör anropet konto.taUt(2000) och den andra anropet konto.taUt(300). Vad händer om koden ser ut så här?

  private int saldo       ;
  public void taUt(int x) {
    saldo=saldo-x         ;}
Satsen ser ut som en enhet men utförs i minst två steg:
  1. Hämta värdet på variabeln saldo.
  2. Lägg tillbaka värdet minus x i saldo.
Låt oss anta att saldot är på 2000 och att båda trådarna hunnit hämta det innan första tråden lägger tillbaka värdet noll. Strax därpå lägger andra tråden tillbaka värdet 1700.
Fråga: Hur mycket förlorar banken? (Svar längst ner.)
I femtio år har man varit medveten om denna risk och många har föreslagit listiga knep för att eliminera problemet, men förgäves. Det går inte att programmera trådsäkert utan en helt ny mekanism i programspråket.

Synkronisering och tjyvstopp

En metod som är synchronized kan bara anropas av en tråd i taget. I vårt exempel blir därför den här koden trådsäker.
  private int saldo                    ;
  synchronized public void taUt(int x) {
    saldo=saldo-x                      ;}
Medan ett anrop till metoden pågår blockeras den och andra anrop får ställa sej och vänta. I själva verket finns det bara ett lås per objekt, så alla synkroniserade metoder blockeras samtidigt. Om den tråd som har nyckeln till låset mitt inne i metoden exekverar satsen wait() lämnar den ifrån sej nyckeln till någon annan tråd och ställer sej själv och väntar. När någon tråd exekverar satsen notifyAll() vaknar objektets alla väntande trådar och en av dom övertar nyckeln. Typfallet är att en tråd vill konsumera något som en annan tråd ännu inte producerat.

I mer komplicerade fall kan det i alla fall bli så att alla står och väntar på varandra, så kallat tjyvstopp (eng deadlock). I bankexemplet skulle det kunna uppträda när man vill föra över pengar mellan två konton. Två trådar kan stå inne i var sitt konto och förgäves försöka komma åt det andra kontot.

Dining philosophers

Det klassiska tjyvstoppsexemplet gäller fem filosofer vid ett runt matbord. Mat finns det gott om men bara fem ätpinnar. Det förnuftiga är förstås att två i taget äter och efter varje munsbit lämnar ifrån sej pinnarna. Men om filosoferna programmeras som trådar kan dom bli sittande med en pinne var och svälta ihjäl i väntan på den andra pinnen!

Att synkronisera ätmetoden skulle visserligen fungera, men då skulle bara en filosof i taget få äta. För att få fram ett förnuftigt beteende måste man införa något extra element, till exempel en servett eller en hela rödvin som går runt enligt några smarta regler. (Tänk ut dom och meddela mej - jag har just gett upp. /Kursledaren)



Svar: Tvåtusen kronor.


Sidansvarig: <henrik@nada.kth.se>
Senast ändrad 10 februari 2002
Tekniskt stöd: <webmaster@nada.kth.se>