14 Trådar Trådar (Thread) Gränssnittet Runnable Kap 14: Sid 2 Med trådar kan man få något att ske vid sidan av det ordinarie programmet. Man kanske vill att en klocka ska stå och ticka, eller att en text ska förflyttas över skärmen. I Java är det enkelt att arbeta med trådaar, Threads Testa klockövningarna i början. Sedan ska du koncentrera dig på övningarna där gränssnittet Runnable ingår. Försök att få ett fungerande Asteroidspel. Försök lösa någon eller några av de uppgifter som presenteras sist i kapitlet. 265333640 © Ove Lundgren 2 Kap 14: Sid 3 Trådar (Thread) De flesta program är en-trådiga: Allt sker i viss ordning. Två processer kan inte ske samtidigt. Java är mång-trådigt (multi-threaded). Det finns möjlighet att arbeta med en eller flera trådar (threads) utöver själva “programtråden”. Man kan dela upp ett program i flera samtidigt exekverande processer. Man kan få flera saker kan ske “samtidigt”. Vi ska låta våra animeringar styras av trådar. tråd Man skapar ett trådobjekt genom att ärva från en klass som heter Thread (tillhör paketet java.lang) Klassen Thread har en metod som heter run(). Den metoden överskuggas: Där beskrivs vad tråden ska göra. Ett trådobjekt har också metoden start()med vilka exekveringen av run()-metoden startas. Ett trådobjekt stoppas genom att sätta dess referens till null (referensen “pekar” inte på något objekt) Övningarna visar hur det hela fungerar: Övning: Klockan blir en tråd Vi skriver en klass, KlockTrad, som ärver av Thread. Koden finns på nästa sida. Ur denna klass ska vi sedan skapa trådobjekt i en applet. (Klassen KlockTrad bygger på klassen Klocka i kapitel 7. Repetera!) Kommentarer till koden: Klassen KlockTrad har instansvariablerna diam för diametern och (x, y) för klockans placering. Vidare är ivall intervallet mellan varje rörelse hos visaren uttryckt i millisekunder. Variabeln sek uttrycker det antal sekunder som klockan visar. Vi deklarerar vi en referens, gr, till ett grafiskt objekt (så att vi kan rita…) I konstruktorn får instansvariablerna (utom sek) sina värden. Metoden setTid() används för att ge variabeln sek ett initialvärde. I metoden visa() ritas själva urtavlan. Visarens poition beror av värdet på variabeln sek så som beskrivs i kapitel 7. Sedan följer run()-metoden som innehåller en beskrivning av vad tråden ska göra: En while(true)-loop är en oändlig loop, det vill säga en loop som kommer att upprepas hur många gånger som helst ( i vårt fall så länge tråden är aktiv). I en sådan loop ligger satser som gör följande: Vi räknar upp en sekund för varje “varv” i loopen med sek++ Metoden visa() anropas varvid urtavlan med visarens nya läge ritas. Klassmetoden Thread.sleep(ivall) ger en fördröjning på ivall millisekunder. Metoden sleep ska ligga i try-catch-konstruktion. Därefter fortsätter loopen, det vill säga en ny urtavla ritas osv, osv. Skriv in koden på nästa sida. Spara som KlockTrad.java och kompilera. 265333640 © Ove Lundgren 3 Kap 14: Sid 4 import java.awt.*; public class KlockTrad extends Thread { private int diam, x, y, sek, ivall; private Graphics gr; public KlockTrad (int d, int a, int b, int f, Graphics g) { diam = d; x = a; y = b; ivall = f; gr = g; } public void setTid(int s) { sek = s; } public void visa() { gr.setColor(Color.white); gr.fillOval(x-diam/2,y-diam/2,diam,diam); gr.setColor(Color.red); gr.drawOval(x-diam/2,y-diam/2,diam,diam); gr.fillOval(x-2,y-2,4,4); double v = - sek*2*Math.PI/60 + Math.PI/2; int r = diam/2-3; // visarens längd int px =x + (int)(r*Math.cos(v)); int py =y - (int)(r*Math.sin(v)); gr.drawLine(x,y,px,py); } public void run() { while(true) { sek++; visa(); try { Thread.sleep(ivall) ; } catch ( InterruptedException e ) { } } } } Nu skriver vi en applet. Kod på nästa sida. Här deklareras ett trådbjekt, alltså ett objekt ur vår klass, KlockTrad, som vi ger referensen u1 Vi deklarerar också ett grafiskt objekt, xg I appletens start()-metod låter vi xg bli referens till appletens grafiska objekt och skapar trådobjektet u1 Vi ger härvid klockan diametern 80 och placerar den i positionen (100,100). Vi anger vidare att klockan ska “ticka fram” en gång var 1000e millisekund och att den ska ritas med grafikobjektet xg (alltså i appleten) Med metoden u1.setTid(0); anger vi att klockan från början ska visa 0 (noll) sekunder. Så anropas tråd-objektets start()-metod, varvid tråden startar ( Trådens run()-metod exekverar ) 265333640 © Ove Lundgren 4 Kap 14: Sid 5 I appletens stop()-metod anropas trådens stop()-metod (så att run() upphör att exekvera) Vidare “dödar” vi grafikobjektet xg. Skriv in koden. Spara som KlockApplet.java och kompilera. Skriv också en html-fil som kan starta appleten. Provkör! Tråden startar. Klockan “tickar”…. import java.applet.*; import java.awt.*; public class KlockApplet extends Applet { private KlockTrad u1; private Graphics xg; public void start() { xg = getGraphics(); u1 = new KlockTrad(80,100,100,1000, xg); u1.setTid(0); u1.start(); } public void stop() { xg.dispose(); u1 = null; } } I KlockApplet, deklarera ytterligare ett KlockTrad-objekt med referensen u2 Instansiera det med t ex u2 = new KlockTrad(50,200,200,100, xg); (Eftersom ivall får värdet 100 så kommer den klockan att gå betydligt snabbare…) Skriv in satserna u2.start(); och u2= null; i respektive metod. Spara, kompilera, provkör. De båda klockorna “tickar” nu helt oberoende av varandra… Övning: Digitalur Skriv en klass, Digitalur, som ärver Thread och visar en digital klocka. Låt appleten KlockApplet skapa objekt ur klassen… Ett lösningsförslag finns på nästa sida… 265333640 © Ove Lundgren 5 Kap 14: Sid 6 // Digitalur.java import java.awt.*; public class Digitalur extends Thread { private int x, y, h, m, s; // Placering samt timma, minut, sekund private Font f; private Graphics gr; public Digitalur(int a, int b, Graphics g) { f = new Font("Courier",Font.BOLD,15); x = a; y = b; gr = g; } public void { this.h = this.m = this.s = } setTid(int h, int m, int s) h; m; s; private String tS(int t) // Metod som gör om ett tal till en { // två-teckens-sträng String s; if(t<10) s = "0"+t; else s=""+t; return s; } public void visa() { gr.setFont(f); gr.setColor(Color.black); gr.fillRect(x-2, y - 15, 85, 20 ); gr.setColor(Color.red); gr.drawString(tS(h)+":"+tS(m)+":"+tS(s),x,y ); } public void run() { while(true) { s++; if (s>59) { s =0; m++;} if (m>59) { m = 0; h++;} if (h>23) h = 0; visa(); try { Thread.sleep(1000) ; } catch ( InterruptedException e ) { } } } } 265333640 © Ove Lundgren 6 Kap 14: Sid 7 Gränssnittet Runnable Det finns ytterligare ett (bekvämt) sätt att arbeta med trådar som man brukar använda bl.a. vid animering. Du vet sedan tidigare att en klass inte kan ärva metoder ur flera klasser, men att man med hjälp av gränssnitt (interface) kan “låna” metoder från andra klasser. Med gränssnittet Runnable kan man förse t ex en applet med metoden run(). Vi skriver alltså public minApplet extends Applet implements Runnable och lägger till run()-metoden: public void run() { ... } Så deklarerar och instansierar vi trådobjekt: private Thread minTraad; ... minTraad = new Thread(this); Observera att vi ska ha argumentet this i Thread-konstruktorn. Med argumentet this anger vi att tråden ska använda den run()-metod som ägs av this (dvs appleten) I appletens start()-metod ska tråden startas ( sker med minTraad.start(); ) och i stop()-metoden ska tråden stoppas ( sker med minTraad= null; ) Övning: RunJumbo Du har i flera övningar flytta omkring stackars Jumbo på olika sätt. I förra kapitlet skrev vi appleten DraJumbo där vi kunde flytta figuren genom att dra musen. Nu ska vi låta animeringen löpa automatiskt med hjälp av en tråd. Ta fram DraJumbo.java och gör ändringar och tillägg enligt koden på nästa sida. Här är några kommentarer till koden: Gränssnittet Runnable implementeras Trådobjeket t deklareras run()-metod i denna applet I appletens init()-metod: Trådobjeketet t instansieras med t = new Thread(this); Variabeln d anger hur mycket figuren ska flyttas i sidled i varje “steg” Trådobjeketet startas med t.start(); ( det vill säga run()-metoden startar…) I appletens stop()-metod: Trådobjektet stoppas med t = null; ( run()-metoden stannar…) I run()-metoden ligger en loop som itererar så länge t inte är null : Här ökar värdet på a (Jumbos horisontella position) Om a blir större än ett visst värde byts tecknet på d (så att figuren flyttas åt vänster nästa gång) Så kommer en fördröjning (Thread.sleep()-metoden i try-catch-konstruktion) Slutligen repaint(); 265333640 © Ove Lundgren 7 Kap 14: Sid 8 import java.awt.*; import java.applet.Applet; public class RunJumbo extends Applet implements Runnable { private int a, b, d; private Image bild1; private Thread t; public void init() { bild1 = this.getImage(getCodeBase(),"jumbo.gif"); t = new Thread(this); a = 50; b = 50; d = 5; t.start(); } public void paint(Graphics g) { g.setColor(Color.blue); g.fillOval(30,30,100,100); g.drawImage(bild1,a,b,this); } public void stop() { t = null; } public void run() { while( t != null ) { a = a + d; if(a>200 || a < 0 ) d=-d; try { Thread.sleep(100) ; } catch ( InterruptedException e ) { } repaint(); } } } Spara som RunJumbo.java Kompilera. Starta appleten med en html-fil Jumbo ”vandrar” fram och tillbaka över appletytan… Bilden ”fladdrar”. Vi ska komplettera med dubbelbuffring strax… Övning: Flytande text. Ta fram en gammal övning där man skriver in något i ett textfält, en beräkning sker och ett resultat skrivs ut. (exempelvis Cirkel.java ) Gör så att texten ”JAVA” sakta förflyttar sig över appleten från vänster till höger i en oändlig loop. Hanteringen ska givetvis skötas av en separat tråd… (Försöker du lägga hanteringen som i en loop i t ex paint()-metoden kommer den ordinarie programtråden hela tiden att vara upptagen och ingen inmatning blir möjlig…) Snygga sedan till appleten: Dubbelbuffra, avrunda beräknade resultat, gör den flytande texten större… 265333640 © Ove Lundgren 8 Kap 14: Sid 9 Övning: En knapp som stannar och startar om I appleten RunJumbo, gör följande tillägg: Importera paketet för händelsehantering: import java.awt.event.*; Implementera gränssnittet ActionListener: Deklarera en knapp: ... implements Runnable, ActionListener private Button butt; Deklarera en logisk variabel: private boolean aktiv = true; Instansiera knappen: butt = new Button("Start/Stop"); och lägg ut den på appleten med add(butt); Sätt lyssnare på knappen: butt.addActionListener(this); Lägg till metoden actionPerformed(): public void actionPerformed(ActionEvent e) { if(aktiv) { t=null; aktiv=false; } else { t = new Thread(this); t.start(); aktiv = true; } } Spara – kompilera – provkör Lägg in dubbelbuffring (Se ”checklistan” för dubbelbuffring i förra kapitlet…) Anmärkning: I Java version 1.1 fanns i klassen Thread metoden stop() för att stoppa en tråd. Vidare fanns metoden suspend() för att tillfälligt deaktivera en tråd samt metoden resume() för att åter aktivera en tråd. Dessa metoder kunde orsaka vissa fel och finns inte med Java2. Metoderna är deprecated (man kan kompilera i Java2, men får ett felmeddelande). Ovanstående metod med t = new Thread(this); t.start(); och t = null; fungerar i både Java och Java2 Övning: Runnable JumboFilm Ta fram övningen JumboFilm (eller JumboFilmDB) från förra kapitlet. I den appleten skapade vi ”filmen” genom att låta paint() anropa sig själv (via repaint() ) Gör ändringar så att filmen i stället styrs av en tråd. Använd en array av Image-objekt för bilderna Använd MediaTracker Lägg in dubbelbuffring. Lägg in en knapp med vilken filmen kan startas/stoppas Spara som JumboFilmRun.java 265333640 (Lösningsförslag på nästa sida…) © Ove Lundgren 9 Kap 14: Sid 10 import java.awt.*; import java.applet.*; import java.awt.event.*; public class JumboFilmRun extends Applet implements Runnable, ActionListener { Image jumbo[] = new Image[6]; Image xbild; Thread t; Graphics xg; Button butt; MediaTracker mt; int i; boolean aktiv = false; public void init() { t = new Thread(this); xbild = createImage(200,200); xg = xbild.getGraphics(); butt = new Button("Film"); add(butt); butt.addActionListener(this); mt = new MediaTracker(this); for(int i=0; i < 6; i++) { jumbo[i] = this.getImage(getCodeBase(),"jumbo"+i+".gif"); mt.addImage(jumbo[i],0); } try{ mt.waitForID(0);} catch(InterruptedException e) {} i = 0; } public void stop() { t = null; public void update(Graphics g) xg.dispose(); } { paint(g); } public void paint(Graphics g) { xg.setColor(Color.white); xg.fillRect(0,0,200,200); xg.setColor(Color.cyan); xg.fillOval(20,20,100,100); xg.drawImage(jumbo[i],50,50,this); g.drawImage(xbild,0,0,this); } public void run() { while(t!=null) { repaint(); try { Thread.sleep( 150) ; } catch ( InterruptedException e ) { } i++; if(i==6) i=0; } } public void actionPerformed(ActionEvent e) { if(aktiv){ t = null; } else { t = new Thread(this); t.start(); aktiv = !aktiv; } } } 265333640 © Ove Lundgren 10 Kap 14: Sid 11 Övning: Asteroiderna – igen! I förra kapitlet kompletterade vi klassen Asteroid så att objektet själv bestämde hur det skulle röra sig. I appleten VisaAsteroider animerade vi genom att röra musen över appletytan. Vi ska skriva om VisaAsteroider så att animeringen i stället drivs av en tråd. Ta fram VisaAsteroider.java. Gör ändringar enligt nedan och spara med namnet RunAsteroider.java // RunAsteroider.java import java.applet.*; import java.awt.*; import java.awt.event.*; public class RunAsteroider extends Applet implements Runnable { Asteroid ast[] = new Asteroid[10]; Thread t; public void init() { t = new Thread(this); for(int i=0;i<10;i++) { ast[i]=new Asteroid( ); } setBackground(Color.black); t.start(); } public void stop() { t = null; } public void paint(Graphics g) { for(int i=0; i<10; i++) { ast[i].visa( g );} } public void run() { while(t!=null) { for(int i=0; i<10; i++) ast[i].nyPosition(); try { Thread.sleep(100) ; } catch ( InterruptedException e ) { } repaint(); } } } Kompilera och provkör! Komplettera med dubbelbuffring. I övningar av typen RunJumbo bestäms figurens läge genom beräkningar i själva appleten. I Asteroid-övningen ovan håller däremot objektet själv reda på sitt läge, när det ska ta en annan riktning mm. Du ska sträva efter att alltid lägga så mycket som möjligt av beräkningar och logik i objektet! Appletens uppgift är att visa och att (eventuellt) ta emot kommandon från användaren. Dessutom utnyttjas appletens run()-metod för att driva t.ex en animering. 265333640 © Ove Lundgren 11 Kap 14: Sid 12 Övning: Asteroidspel Skriv en applet, Asteroidspel. Utgå från RunAsteroid.java. Komplettera med kod från VisaRymdskepp.java (kapitel 8 och 10) Gör så att din nya applet visar asteroiderna samt ditt rymdskepp (kapitel 8 och 10) Rymdskeppet ska kunna förflyttas i sidled med piltangenterna. Skott ska kunna avlossas med SPACE-tangenten. Vi kompletterar så att en asteroid känner av när de träffats: I klassen Astreoid: Deklarera dessa variabler: private boolean traffad=false; public static int antalast; // blir true om asteroid träffats // antalet “levande” asteroider I visa()-metoden: Gör så att asteroiden visas endast om den inte är träffad: if(!traffad){ ... } Lägg till denna metod: public void traff(int p, boolean sk) { if (!traffad && sk) { traffad = p > xpos - diam/2 && p < xxpos + diam/2; if(traffad)antalast--; } } p är x-koordinaten för skottbanan. sk är sann om ett skott avlossas traffad blir true om p är ungefär lika med x (asteroidens horisontella position) Om asteroiden träffats räknas antalast ned med 1 (ett) I klassen Asteroidspel: Asteroid.antalast = 10; // Sätt det antal asteroider som skapas Komplettera paint() : public void paint(Graphics g) { this.requestFocus(); xg.setColor(Color.black); Anropar respektive asteroids metod traff xg.fillRect(0,0,300,300); r.visa(xg); if (skott) r.skjut(xg); for(int i=0; i<10; i++) {ast[i].traff(x, skott); ast[i].visa( xg );} xg.setColor(Color.red); xg.drawString("Antal asteroider kvar: "+ Asteroid.antalast,10,30); g.drawImage(xbild,0,0,this); } 265333640 © Ove Lundgren 12 Kap 14: Sid 13 Spelet kan givetvis kompletteras på olika sätt: Astreroiderna “exploderar” när de träffats.. Rymdskeppet känner av om det är träffat. Texter som talar om att “Du har vunnit” resp. “Du har förlorat” Ljud etc… Övning: Klot Skapa först en klass Klot Ett Klot-objekt ska kunna instansieras med parametrar för diameter (diam) och färg (farg). I konstruktorn ska klotens läge (x, y) slumpas fram liksom värden på klotens förflyttning horisontellt och vertikalt (dx och dy ) (Påminner mycket om klassen Asteroid, men ett klot ska även kunna förflytta sig uppåt…) Klassen ska ha metoden nyPosition() där en ny position bestäms. Skriv denna så att ett klot kommer att”studsa” mot appletens kanter. (En asteroid vänder ju utanför appleten) Klassen ska ha en metod visa(Graphics g) som visar klotet. Skriv en “runnable” applet, KlotRun, där du instansierar ett Klot-objekt. I run()-metoden anropas klotets metod nyPosition() och repaint() (samt fördröjning) I paint()-metoden anropas klotets visa()-metod Skapa sedan en array av klot… Övning: Snöfall (påminner om ovanstående) Utgå från klassen Star (kap 7) och skriv klassen SnoFlinga. Storlek, antal "uddar", position samt dx- och dy-värden ska slumpas vid instansiering. Klassen ska ha metoden nyPosition() där en ny position bestäms och en metod visa(Graphics g) som visar flingan. Skriv en applet Snofall där en array av flingor skapas så att ett snöfall visas. Här är fler övningar på trådar: Reklamskylt Gör en applet som visar en text som flyttas över skärmen mot bakgrund av en cirkelskiva med varierande storlek. Tennis Gör en applet där du spelar tennis mot en “garageport”. Du ska kunna flytta din “racket” med piltangenterna. Studsboll Skriv en applet där en boll kan släppas från viss höjd, faller och studsar mot “golvet”. (Utnyttja lagar från fysiken för att få en naturlig rörelse) Artilleri Skriv en applet där där en kanonkula kan skjutas ur en kanon. Kanonen ska kunna ställas in i olika vinklar och ges olika laddning. (Utnyttja lagar från fysiken för att få en naturlig rörelse) Kompassnål En kompassnål visas (står och svänger) i en applet. När du håller ned musknappen och drar ska nålan riktas in mot muspilen. Skapa sedan en array av kompassnålar… Bungy-jump Låt din streckgubbe (kapitel 4) hoppa bungy-jump… 265333640 © Ove Lundgren 13