{ "title": "Tråde", "exercisesLink": "opgaver/opgaver.htm" }
Kompliceret Fler-trådede programmer er mere komplicerede at forstå, og vanskeligere at konstruere end almindelige enkelt-trådede programmer, som vi hidtil har beskæftiget os med.
Efterligner fortolker Vi har sikkert alle prøvet at forstå "hvad et program gør", ved hjælp af en udskrift af kildeteksten. Med en finger markerer man løbende hvor i programmet man er kommet til, samtidig med, at man noterer sig værdierne af de forskellige variable. Det man prøver at efterligne er program-udførelsen. Man optræder i rollen som fortolker.
Tilstande Når vi skal til at arbejde med fler-trådede programmer får vi, populært sagt, brug for flere fingre. Inden vi fortsætter med at se hvad en tråd er, samt hvad det vil sige at have flere af dem, vil vi lægge et fundament for vores forståelse. Vi vil fastlægge begrebet tilstand som spiller en central rolle i vores bestræbelser på senere at arbejde med fler-trådede programmer.
 

 

1. Tilstande

  Det mest grundlæggende man kan beskrive som havende en tilstand, er en variabel.
Variabel

Definition: En variabels tilstand

En variabels tilstand, er dens værdi.

Tilstand kan være udefineret En variabels tilstand behøver ikke være defineret. Når man erklærer en lokal variabel i en metode, uden at tildele den en initiel værdi, er den nok erklæret, men den har ikke nogen værdi - dens tilstand er udefineret. De fleste compilere vil i større eller mindre omfang kontrollere at alle variable har en veldefineret tilstand før de anvendes (med mindre anvendelsen netop tildeler dem en værdi).
  Modsat lokale variable er instans-variable altid definerede. I Java tildeles alle primitive instans-variable (dvs. dem der ikke er reference-variable) en værdi svarende til 0, mens reference-variable sættes til null (hvilket også svarer til 0; hvis man tænker C/C++). På den måde er instans-variable definerede, idet de erklæres. Det er dog pænere først at tænke på instans-variable som definerede efter konstruktoren er udført, da det netop er formålet med konstruktoren at foretage denne initialisering.
   
  Et objekts tilstand kan beskrives ud fra definitionen på en variabels tilstand. Det er instans-variablene der beskriver et objekts tilstand, og samlet set er objektets tilstand derfor lig instans-variablenes tilstand.
Objekt

Definition: Et objekts tilstand

Et objekts tilstand består af instans-variablenes tilstande.

  Bemærk at lokale variable (og parametre) i instans-metoderne ikke er en del af objektets tilstand. Deres eksistens er midlertidig, og derfor vanskelige at holde rede på, set fra objektets synspunkt. Det er ikke objektet, der har styr på om de findes! Metoder kaldes andre steder fra, og det er dér kontrollen med deres eksistens hører hjemme. Vi skal senere inddrage disse variables tilstande i vores forståelse af tilstande.
   
  Med et objekts tilstand fastlagt, kan vi gå videre til det samlede objektsystems tilstand. Det er givet ved samtlige indgående objekters tilstand, der som et samlet hele udgør objektsystemets tilstand.
Objektsystem

Definition: Et objektsystems tilstand

Et objektsystems tilstand består af de indgående objekters tilstand.

   
  De foregående tre definitioner på tilstande har bygget videre på hinanden, og den fjerde definition, som vi vil slutte dette afsnit af med, gør også dette - men den gør mere end det.
  Det vi skal definere er selve programmets tilstand - vi skal have programmets udførelse ind i billedet! De foregående tilstande har alle haft en vis persistent natur - de har udemærket kunnet leve med en uændret tilstand, men det kan vi ikke nøjes med - et program skal gøre noget!
Midlertidige variable Derfor skal vi have de midlertidige variable (lokale variable og parametre) ind i billedet. Det er de midlertidige variable der typisk "bevæger sig hurtigst", mens instans-variablene ofte nyder gavn af de resultater, der derved fremkommer. Dette med "hastigheden" er en meget generel betragtning som man ikke skal lægge al for megen vægt på, men den leder tanken hen på at de midlertidige variable er mere dynamiske i deres natur. Det er netop dynamikken vi skal beskæftige os med, når vi taler om programmets udførelse, og det er derfor naturligt, at det er her de midlertidige variables tilstande træder ind i billedet.
Hvor er vi? En anden ting vi skal have beskrevet med programmets tilstand er hvor vi er kommet til i udførelsen - hvor "fingeren" er nået til! At det sted vi er kommet til ændrer sig under programmets udførelse indeholder kernen af begrebet dynamisk - vi bevæger os!
  Med disse to nye elementer er vi nået frem til definitionen på et programs tilstand
Enkelt-trådet program

Definition: Et (enkelt-trådet) programs tilstand

Et programs tilstand består af objektsystemets tilstand, alle lokale variables tilstande, og det sted programmet er kommet til i sin udførelse.

  Vi ser her at definitionen lyder på "Et enkelt-trådet programs tilstand". Det skyldes at vi senere skal se en mere dækkende definition på et programs tilstand. I den forbindelse er det godt, at vi allerede her slår fast, at definitionen kun holder for enkelt-trådede programmer - den slags programmer vi hidtil har arbejdet med!
 

 

1.2 Tråde og tilstande

Back to basic Vel udrustede med ovenstående definitioner, vil vi nu igen vende os mod vores manuelle fortolkning med blyant/papir og en finger i kildeteksten.
   
  Vi har tidligere beskrevet hvordan et program udføres, ved at angive hvordan forløbet bevæger sig gennem kildeteksten. I kapitlet "Metoder" i afsnittet "Grundlæggende programmering" brugte vi en række figurer til at beskrive hvad der sker ved metodekald:
Figur 1:
Forløb af metodekald vist med pile
Pile I denne figur angiver pile hvordan vi bevæger os rundt i kildeteksten (de grønne felter).
   
  Det er intuitivt let at forstå et programs forløb ved at tegne pile i kildeteksten, og man bruger ofte at lave følgende tegning på tavlen når man skal forklare betydningen af ordet: "løkke", i forbindelse med iteration:
Figur 2:
Rød tråd i program
Løkke Den røde pil illustrerer med sit forløb, hvorfra betegnelsen "løkke" stammer (eng.: loop), men den gør mere end det - den illustrerer også hvorfor man kalder en tråd: en tråd!
  Eftersom vi i det følgende skal definere tilstanden af et fler-trådet program, kan vi passende starte med at fastlægge hvad en enkelt tråds tilstand er. Det der kendetegner en tråd er ikke alene hvor i programmet vi er kommet til, men også de lokale variables tilstande.
Tråd

Definition: En tråds tilstand

En tråds tilstand består af alle lokale variables tilstand (i tråden) og det sted tråden er kommet til, i sin udførelse.

  Vi ser her at definitionen på en tråds tilstand ikke indbefatter objektsystemet. Før man begyndte at programmere med objekter (den strukturerede programmering) kunne man stort set have brugt definitionen på en tråds tilstand, som definition på hele programmets tilstand (med den forskel at de globale variables tilstand også skal med).
  Det er derfor ikke overraskende at vi nu er meget tæt på den endelige definition på et programs tilstand - idet vi blot skal have objektsystemet med:
Fler-trådet program

Definition: Et (fler-trådet) programs tilstand

Et (fler-trådet) programs tilstand består af objektsystemets tilstand og alle trådenes tilstande.

  Vi ser her at vores adskillelse af objekterne og trådenes tilstande, gør det enkelts af definere et flertrådet programs tilstand, og som vi skal se i dette og andre kapitler gør det også vores beskrivelse af diverse problemstillinger mere enkel.
 

 

2. Tråde i Java

  Der er ikke noget så godt som en række definitioner - det skulle da lige være noget kode, der kan gøre det levende! Lad os se hvordan vi kan lave flertrådede programmer i Java.
  Vi har hidtil lavet mange programmer, der alle har været enkelt-trådede. Ethvert program starter med at være enkelt-trådet; hvorefter det kan vælge at gøre sig fler-trådet. Der er ikke nogen sproglig konstruktion i Java, der understøtter skabelsen af tråde - derimod er der en klasse: Thread, som hjælper os. Det der i bund og grund hjælper os, er noget native kode, der findes i implementationen af denne klasse. Man skal derfor ikke regne med at se noget nyt og spændende kode, hvis man kigger i kildeteksten til java.lang.Thread, den metode: start, der indeholder magien, er nemlig erklæret native.
  Der er to metoder i Thread-klassen, der i første række interessere os - nemlig metoderne: start og run. start er som nævnt native, mens vi selv skal implementere run. Sammenhængen mellem disse metoder gør det muligt for at få flere tråde igang.
 

 

2.1 "To måder = en måde"

To måder Uanset hvilken litteratur man måtte vælge at læse, når man vil lære at programmere med tråde i Java, vil man blive præsenteret for to forskellige måder at implementere dem på. Vi vil også her se de to måder, men vil efterfølgende se at de i virkeligheden dækker og én og samme måde.

 

2.1.1 Subklasse af Thread

  Den første måde har vi allerede lagt op til i det foregående, idet vi nævnte at man selv skulle implementere run-metoden. Lad os se et simpelt eksempel.
Kildetekst 1:
Hello World á la Threads
          public class VorThread extends Thread {
  
            public void run() {
              System.out.println( "Hello world of threads" );
            }
          }
        
          public class Main {
            
            public static void main( String[] argv ) {
              
              VorThread tråd = new VorThread();
              tråd.start();
            }
          }          
        
          Hello world of threads
        
Vi har her lavet en subklasse: VorThread til Thread-klassen. I den har vi implementeret run-metoden, med en banal udskrift. I vores testanvendelse laver vi en instans af VorThread og kalder start-metoden på denne.
Når man ser udskirften, der tydeligvis viser at run-metoden er blevet udført, skal der ikke megen fantasi til at gennemskue, at start-metoden kalder run-metoden - og det er da også ganske korrekt.
To tråde Det man ikke kan se, er at der skete noget magisk - at der et ganske kort øjeblik fandtes to tråde i vores program. Denne magik skyldes implementationen af start-metoden. Lad os først prøve at gøre trådenes eksistens mere tydelig, inden vi præciserer hvad der sker i start-metoden.
 
sleep-metode Når man skal illustrere effekten af flere tråde i et program, er det en fordel at kunne få de enkelte tråde til at "sove", da deres samtidighed ellers ikke er synlig. I Thread-klassen er der da også en statisk sleep-metode, men den er træls at bruge. Det skyldes, at den kan kaste en InterruptedException, som vi derfor altid skal lave en tom try-catch til. Da den reelt aldrig bliver kastet i vores små eksempler, er det ekstra træls.
Sleeper For at undgå dette går vi så langt som at lave en klasse: Sleeper, med statiske metoder. I Sleeper laver vi en række nyttige sleep-metoder, der egner sig til vores testformål.
Kildetekst 2:
Nyttige sleep-metoder
          public class Sleeper {
  
            public static double sleepInterruptable( double sekunder ) {
              long start = System.currentTimeMillis();
          
              try {
                Thread.sleep( (long) (sekunder * 1000) );
              } catch ( InterruptedException e ) {
                // accept interruption
              }
          
              return ( (double) System.currentTimeMillis() - start ) / 1000;
            }
            
            public static double sleep( double sekunder ) {
              double delta=0;
          
              while ( delta < sekunder )
                delta += sleepInterruptable( sekunder - delta );
              
              return delta;  
            }
            
            public static double nap() {
              return sleep( 0.1 );
            }
            
            public static double sleepRandom( double sekunder ) {
              return sleep( sekunder * Math.random() );
            }
            
            public static void wait( Object obj ) {
              try {
                obj.wait();
              } catch ( InterruptedException e ) {
                // ignore
              }
            }
          }
        
[ovenstående udgave er implementeret så den med sleep-metoden garanterer at InterruptedException ikke kan afkorte pausen, men det er mest for sjovt - det ville nok være mere pædagogisk at lave en simplere udgave!]
Se indtil videre bort fra wait-metoden. Den vender vi tilbage til i kapitlet: "Kritiske regioner" i afsnittet: "Monitorer i Java".
På dette sted er vi interesseret i de tre sleep-metoder:
double sleep( double sekunder )
double sleepRandom( double sekunder )
double nap()
sleep-metoden holder en pause i eksekvering af den tråd der udfører kaldet, på det angivne antal sekunder. sleepRandom holder en pause af tilfældig længe, i intervallet [0:sekunder]. Endelig er der nap-metoden (dk.: lur), der holder en kort pause på 1/10 sekund; hvilket vi bruger jævnligt i eksempler.
Alle tre metoder returnerer hvor længe der reelt er "sovet".
Specielt den metode, der holder en pause af tilfældig længe, er nyttig til at illustrere afviklingen af flere tråde. Lad os gentage eksemplet ovenfor, idet vi lader VorThread anvende Sleeper, og samtidig indfører et ID, til at skelne flere tråde fra hinanden.
Kildetekst 3:
Flere tråde der starter med pause
          public class VorThread extends Thread {
            private int id;
            
            public VorThread( int id ) {
              this.id = id;
            }
            
            public void run() {
              Sleeper.sleepRandom( 3 );
              System.out.println( "Hello I'm thread no.: " + id );
            }
          }
        
          public class Main {

            public static void main( String[] argv ) {
          
              for ( int i=0; i<5; i++ ) {
                VorThread tråd = new VorThread( i );
                tråd.start();
              }
            }
          }        
        
          Hello I'm thread no.: 0
          Hello I'm thread no.: 3
          Hello I'm thread no.: 4
          Hello I'm thread no.: 1
          Hello I'm thread no.: 2
        
I for-løkken instantierer vi fem tråd-objekter og starter dem løbende. Hver at tråd-objekternes run-metoder starter med at holde en tilfældig pause på op til 3 sekunder. I udskriften bemærker man hvorledes denne pause, bevirker at udskrifterne ikke udføres i samme rækkefølge som trådene er kommet til verden.
F.eks. kan vi se at tråd 4 udfører sin udskrift før tråd 1 og 2. Samtidig ved vi fra for-løkken at tråd 1 og 2 ikke alene instantieres, men også startes før tråd 3 - ergo har vi demonstreret at der rent faktisk har eksisteret flere samtidige tråde.
Dette bringer os tilbage til spørgsmålet om forholdet mellem run- og start-metoden i et tråd-objekt. Som vi allerede har konstateret kalder start-metoden run-metoden. I start-metoden sker det magiske at den både kalder run-metoden og returnerer på en gang! Det gør den ved at lave en ny tråd: én der kalder run-metoden; hvorefter den oprindelige tråd blot kan returnere. Der ventes derfor ikke på returnering fra kaldet af run-metoden, før der returneres fra start-metoden.
Man kan beskrive dette med en to-fork, der deler sig i to:
Figur 3:
Den magiske gaffel
Vi har nu set den ene måde, hvorpå man kan lave flere tråde i sine programmer, men den har en klar ulempe - den kræver at vi nedarver fra Thread-klassen; hvilket ikke er uden omkostninger i et sprog der ikke tillader multipel nedarvning.

 

2.1.2 Implementation af interfacet: Runnable

Alternativet til at nedarve fra Thread, er at implementere interfacet: java.lang.Runnable:
Kildetekst 4:
interface Runnable
          public interface Runnable {

            public abstract void run();
          }
        
Vi genkender her straks run-metoden, som vi implementerede ovenfor, i forbindelse med at vi nedarvede fra Thread. Det kommer derfor sikkert heller ikke som nogen overraskelse, at Thread rent faktisk selv implementerer dette interface.
Runnable har brug for hjælp Umiddelbart lugter det af det rigtige, men der mangler noget - det er jo start-metoden der indeholder magien, og Runnable-interfacet er i sig selv virkningsløst. Løsningen skal findes i en af Thread-klassens konstruktorer:
Thread( Runnable target )
Denne konstruktor tager en Runnable som parameter, og det er instansen af Thread der leverer start-metoden, og dermed magien.
Lad os se hvordan vores eksempel med nedarvning i stedet kan laves med en implementation af Runnable-interfacet:
Kildetekst 5:
Flere tråde der starter med pause
          public class VorRunnable implements Runnable {
            private int id;
            
            public VorRunnable( int id ) {
              this.id = id;
            }
            
            public void run() {
              Sleeper.sleepRandom( 3 );
              System.out.println( "Hello I'm thread no.: " + id );
            }
          }
        
          public class Main {

            public static void main( String[] argv ) {
          
              for ( int i=0; i<5; i++ ) {
                Thread tråd = new Thread( new VorRunnable( i ) );
                tråd.start();
              }
            }
          }        
        
          Hello I'm thread no.: 0
          Hello I'm thread no.: 3
          Hello I'm thread no.: 4
          Hello I'm thread no.: 1
          Hello I'm thread no.: 2
        
Vi bemærker i Main, at skabelsen af tråde, nu går via Thread-objektets start-metode, selvom det er run-metoden i VorRunnable, der udføres. Dette kan give anledning til at spørge: "Hvordan ser Thread-klassens egen implementation af run-metoden ud?". Det er dette spørgsmål vi vil berøre i det følgende afsnit.

 

2.1.3 "Det samme"

Betragt følgende skitsering (meget er udeladt) af klassen: java.lang.Thread:
Kildetekst 6:
Skitsering af Thread-klassen
          public class Thread implements Runnable {
            private Runnable target;
            
            public Thread() {
              this( null );
            }
            
            public Thread( Runnable target ) {
              this.target = target;
            }
            
            /*
             * Dette er en magisk metode, der kalder objektets
             * egen run-metode _samtidig med_ at den returnerer.
             * På den måde bliver der to tråde: Den oprindelige
             * (der returnerer) og den nye (der kører i run-
             * metoden)
             */
            public synchronized native void start() {
              // indsæt magi her!
            }
            
            public void run() {
              if ( target != null )
                target.run();
            }
          }
        
Man laver normalt ikke en instans af Thread uden at angive en Runnable som parameter til konstruktoren. Default-konstruktoren er derfor tiltænkt sub-klasser. Såfremt man laver en subklasse til Thread vil target være null, og et kald af den nedarvede start-metode vil forløbe som følger:
Figur 4:
Når target er null
Man bemærker at start- og run-metoderne indgår i et Template Method design pattern, idet start-metoden er template-metode, og run-metoden er hook-metode. Dette gør sig naturligvis kun gældende, når vi nedarver fra Thread.
Hvis vi i stedet bruger Thread til at starte den nye tråd, men ellers lader den delegere kaldet videre til run-metoden i vores Runnable, ser billedet således ud:
Figur 5:
Når target ikke er null
[Da jeg tegnede denne fgur glemte jeg at skrive "Runnable" over objektet til højre, på samme måde som der står "Thread" over objektet til venstre]
Her går kaldet stadig til Thread's run-metode, men denne gang er den ikke overrided af en subklasse, og den delegerer derfor kaldet videre til vores Runnable.
Vi kan også beskrive sammenhængen, mellem Thread og Runnable, med følgende klassediagram:
Figur 6:
Thread og Runnable

 

2.2 Schedulering

Time-slice Vi skal ikke her gøre et større nummer ud af schedulering, men blot berøre det faktum, at man med en CPU ikke reelt kan udføre flere tråde på én gang. I stedet gøres det ved hurtigt at skifte med eksekvering af de enkelte tråde. Hver tråd tildeles en tidsperiode, den får lov at udnytte, før den næste tråd kommer til. Denne tidsperiode kalde en time-slice.
 

Definition: Time-slice

Den tidsperiode som schduleren tildeler en tråd, hvor tråden er den eneste der kører på CPU'en

Når tråden har opbrugt sin time-slice, vil scheduleren skifte til den næste tråd, der får tildelt en time-slice, og så fremdeles:
 

Definition: Preemption

Når en tråd har opbrugt sin time-slice, bliver den preempted, og den næste tråd får i stedet tildelt en time-slice, og bliver ligeledes preempted når denne er opbrugt, og så fremdeles.

I det næste kapitel: "Kritiske regioner", skal vi se hvilke problemer det kan give, hvis en tråd bliver preempted, mens den er ved at udføre en operation.
 
Frivillig og tvungen tidsdeling Med preemption er det scheduleren der bestemmer hvornår tråden skal afgive processoren. Man kalder det derfor tvungen tidsdeling, i modsætning til frivillig tidsdeling; hvor en tråd selv bestemmer hvornår den vil afgive processoren. I det sidste tilfælde er der derfor ingen time-slices, man får ganske enkelt overdraget processoren indtil man selv giver den tilbage til scheduleren. Frivillig tidsdeling anvendes sjældent i vore dage.
 
En tråd kan tilbyde scheduleren at give afkald på resten af sin igangværende time-slice, ved at kalde metoden:
Thread.yield()
Det er dog ene og alene op til scheduleren at afgøre, hvad den vil gøre, men man kan normalt regne med at en anden tråd - hvis en sådan findes - får tildelt en time-slice. Dette vil under alle omstændigheder kun ske, hvis der er andre tråde af samme prioritet. Vi skal i det følgende afsnit se nærmere på tråde og prioriteter.

 

2.3 Prioriteter

Som det kendes fra operativ-systemer; hvor processer kan have forskellige prioriteter, har tråde også prioriteter. Det betyder, at man kan lade nogle tråde få forret til at køre på CPU'en, mens andre må vente - med deres knap så vigtige/kritiske arbejdsopgaver.
I forbindelse med prioriteter, har Thread-klassen følgende set-/get-metoder:
void setPriority( int priority )
int getPriority()
Thread-klassen har tre konstanter, der anvendes til at fastlægge de mulige prioriteter, samt default-prioriteten:
MAX_PRIORITY
MIN_PRIORITY
NORM_PRIORITY
Det betyder at priority skal ligge i intervallet: [MIN_PRIORITY : MAX_PRIORITY]
Default-prioriteten, der som udgangspunkt tildeles alle nye tråde er: NORM_PRIORITY.
Hvis to tråde T1 og T2 har prioriteterne P1 og P2, og P1<P2, vil T2 altid have forret til CPU'en, og så længe den ønsker at bruge CPU'en vil T1 ikke få adgang til den.

 

2.4 Hvordan stopper man en tråd?

Hvis man havde stillet dette spørgsmål i Java's barndom havde svaret været - man kalder da stop-metoden i Thread-klassen! Metoden terminerer tråden omgående, og hvorfor Sun placerede en sådan stop-metode i Thread-klassen er lidt af en gåde - det må skyldes at man har arbejdet "på tom mave".
Problemet med blot at terminere tråde uden videre, er at det kan efterlade de data/objekter som tråden arbejdede med i en inkonsistent tilstand. Det er ganske enkelt ikke en holdbar måde at opnå terminering af tråde, og stop-metoden er da også siden blevet erklæret depricated, og man fraråder dens brug.
Som vi senere skal se, er det desværre ikke den eneste bummert Sun har lavede i Thread-klassen - det bliver endnu værre! Man kan læse Sun's egen beskrivelse problemerne, og deres løsning, ved at finde stop-metoden i dokumentationen, der følger med J2SDK - der er en link til fra denne beskrivelse der hedder: "Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?".
 
Men hvordan løses problemet så? Man gør det ved at sende en request til tråden om at stoppe - når det passer den! Lad os se hvordan dette kan implementeres:
 

 

2.4.1 Shutdownable-interfacet

  I større programmer, der arbejder med tråde (typisk netværksprogrammer), plejer jeg at indføre et interface, som jeg lader relevante Thread-klasser implementere:
Kildetekst 7:
interface Shutdownable
          public interface Shutdownable {
  
            public void shutdown();
          }
        
  Thread-klasser, der implementerer dette interface skal afslutte deres arbejde og terminere. Metoden venter ikke på at dette sker, men returnerer med det samme.
  Lad os se et eksempel:
Kildetekst 8:
Tråd der kan lukkes ned
          public class RunningThread extends Thread implements Shutdownable {
            private boolean shutdown;
            
            public RunningThread() {
              shutdown = false;
            }
            
            public void run() {
              while ( !shutdown ) {
                System.out.println( "running..." );
                Sleeper.sleep( 2 );
              }
              
              System.out.println( "shutdown in progress" );
              
              // afslut tråden
              Sleeper.sleep( 2 );
              
              System.out.println( "stopped" );
            }
            
            public void shutdown() {
              shutdown = true;
              System.out.println( "shutdown requested" );
            }
          }
        
  Man bemærker at vi her, som det ofte vil være tilfældet, har vlagt at implementere Shutdownable med anvendelse af en boolean, der lægger en besked til tråden om at den skal terminere.
   
  Lad os se en testanvendelse, hvor vi lader en runner løbe lidt, før vi sender den en request om at stoppe:
Kildetekst 9:
Terminering af tråd
          public class Main {

          public static void main( String[] argv ) {
            RunningThread runner = new RunningThread();
            runner.start();
            
            Sleeper.sleep( 5 );
            
            runner.shutdown();
            
            System.out.println( "main stopped" );
          }
        }
        
          0: running...
          2: running...
          4: running...
          5: shutdown requested
          5: main stopped
          6: shutdown in progress
          8: stopped
        
Vi har til venstre i udskriften indsat en tidslinie, der beskriver hvornår de enkelte linier udskrives (i sekunder). Denne del af "udskriften" er ikke lavet af programmet. Man bemærker specielt, at der går et sekund fra requesten om at stoppe tråden modtages, til tråden stopper.
Man bemærker at main-tråden stopper umiddelbart efter shutdown-kaldet returnerer.
To-fase terminering Denne teknik med at sende tråden en request om at terminere, og lade den foretage et shutdown, uden at vi venter på det sker, kaldes to-fase terminering. "To-fase", fordi den foregår i to faser: første at vi giver tråden besked om at den skal terminere, og dernæst at den gør det (senere).

 

2.4.2 join

Som nævnt er idéen med Shutdownable-interfacet, at shutdown-metoden normalt ikke venter på at tråden terminerer, men returnerer med det samme. Men hvad gør man generelt, hvis man ønsker at "hægte sig på en tråd" for at vente på at den terminerer?
Til dette formål har Thread-klassen en join-metode. Lad os se hvad der sker, hvis vi kalder join-metoden på vores runner i eksemplet ovenfor, efter vi er returneret til main efter shutdown-kaldet:
Kildetekst 10:
Terminering af tråd med join
          public class Main {

            public static void main( String[] argv ) {
              RunningThread runner = new RunningThread();
              runner.start();
              
              Sleeper.sleep( 5 );
              
              runner.shutdown();
          
              try {
                runner.join();
              } catch ( InterruptedException e ) {
                // ignore
              }
              
              System.out.println( "main stopped" );
            }
          }
        
          0: running...
          2: running...
          4: running...
          5: shutdown requested
          6: shutdown in progress
          8: stopped
          8: main stopped
        
Man bemærker i modsætning til eksemplet ovenfor, at main-tråden ikke terminerer før vores runner er termineret - den venter på den!

 

2.5 Daemon-tråde

Thread-klassen har en metode:
void setDaemon( boolean on )
Der kan markere en tråd som værende en daemon-tråd. Java's virtuelle maskine vil terminere når der ikke er nogen almindelige tråde tilbage - dvs. når der kun er daemon-tråde tilbage (eller slet ingen).
Det betyder i praksis, at markerer man en tråd som værende daemon-tråd, betyder det: "Du skal ikke holde programmet kørende for min skyld!". Denne "jeg lever kun for de andre tråde", er den eneste specielle egenskab ved en daemon-tråd - ellers er den en tråd som alle andre.
 

Nedenstående program lavede jeg for nogle år siden, og det demonstrerer nogle tråde der tæller. Jeg ved ikke rigtig, hvor jeg vil bruge det som eksempel, så indtil videre står det her:

 

Kildetekster

Thread Monitor

thread_monitor.zip