Trådar i Java. Johan Ånäs. Seminarieuppsats Institutionen för informationsbehandling Åbo Akademi

Relevanta dokument
Trådar. Aktiva objekt

1.1 Runnable och Thread

Realtidssystem. - Dödläge - EDAF85 - Realtidssystem (Helsingborg) Elin A. Topp. Föreläsning 5

Concurrency Saker händer samtidigt. Process En instans av ett program

Objektorienterad Programkonstruktion. Föreläsning dec 2015

DI-institutionen Sid 1 av 6 Hans-Edy Mårtensson Sten Sundin

LÖSNINGSFÖRSLAG Programmeringsteknik För Ing. - Java, 5p

Fö 5+6 TSEA81. Real-time kernel + Real-time OS

Classes och Interfaces, Objects och References, Initialization

Realtidssystem. - Semaforer, trådsynkronisering - EDAF85 - Realtidssystem (Helsingborg) Elin A. Topp. Föreläsning 2

Java: kort introduktion. Trådar. Något om mutex, Dekkers algoritm och monitorer. Javas ("inbyggda monitor") synchronized.

Föreläsning 2. Operativsystem och programmering

Objektorienterad Programkonstruktion. Föreläsning 11 6 dec 2016

Exam Concurrent and Real-Time Programming

Objektorienterad Programkonstruktion, DD1346 FACIT. Tentamen , kl

Realtidssystem. - Semaforer, trådsynkronisering - EDA698 - Realtidssystem (Helsingborg) Elin A. Topp

Kompilering och exekvering. Föreläsning 1 Objektorienterad programmering DD1332. En kompilerbar och körbar java-kod. Kompilering och exekvering

Objektorientering: Lagring, räckvidd och livstid

Lite om felhantering och Exceptions Mer om variabler och parametrar Fält (eng array) och klassen ArrayList.

Föreläsning 15: Parallella subrutiner. Parallellitet. Varför parallella underprogram?

Classes och Interfaces, Objects och References Objekt-orienterad programmering och design (DIT952) Niklas Broberg, 2016

Arv. Fundamental objekt-orienterad teknik. arv i Java modifieraren protected Lägga till och modifiera metoder med hjälp av arv Klass hierarkier

Kopiering av objekt i Java

Java TRÅDAR OCH GRAFIK. FAdil galjić

Objektorientering: Lagring och livstid

Mekanismer. (implementation)

Högskolan Dalarna sid 1 av 7 DI-institutionen Hans-Edy Mårtensson Sten Sundin

Operativsystem ID2200 Tentamen TEN1 3.8 hp :00-18:00

Uppgiften är att beskriva en kvadrat i ett Java program. En första version av programmet skulle kunna se ut så här:

Arv: Fordonsexempel. Arv. Arv: fordonsexempel (forts) Arv: Ett exempel. En klassdefinition class A extends B {... }

Inledande programmering med C# (1DV402) Introduktion till C#

Kort om klasser och objekt En introduktion till GUI-programmering i Java

F4. programmeringsteknik och Matlab

FÖRSLAG TILL LÖSNINGAR FÖR TENTAMEN I INTERNETPROGRAMMERING MED JAVA, 5p för SY , kl

Vem är vem på kursen. Objektorienterad programvaruutveckling GU (DIT011) Kursbok Cay Horstmann: Big Java 3rd edition.

Objektorienterad Programkonstruktion, DD1346. Tentamen , kl

Tung bakgrundsaktivitet t.ex. Aktiva objekt t.ex. Animering, simulering. DD2385 Programutvecklingsteknik Några bilder till föreläsning 9 6/5 2013

Lunds Tekniska Högskola Datorarkitektur med operativsystem EITF60. Superscalar vs VLIW. Cornelia Kloth IDA2. Inlämningsdatum:

Synkronisering. Ordning och reda

Java, klasser, objekt (Skansholm: Kapitel 2)

Programmering med Java. Grunderna. Programspråket Java. Programmering med Java. Källkodsexempel. Java API-exempel In- och utmatning.

TUTORIAL: KLASSER & OBJEKT

Klassen javax.swing.timer

Tentamen Nätverksprogrammering Lösningsförslag

Tentamen i TDP004 Objektorienterad Programmering Lösningsförslag

TENTAMEN. Kurs: Objektorienterad programmeringsmetodik 5DV133 Ansvarig lärare: Anders Broberg. VT-13 Datum: Tid: kl

Objektorienterad programmering i Java I

Parallellism, återblick

TDDC77 Objektorienterad Programmering

UML. Klassdiagr. Abstraktion. Relationer. Överskugg. Överlagr. Aktivitetsdiagram Typomv. Typomv. Klassdiagr. Abstraktion. Relationer.

Objektorienterad programmering

Klassdeklaration. Metoddeklaration. Parameteröverföring

Synkronisering. Föreläsning 8

Grundläggande programmering, STS 1, VT Sven Sandberg. Föreläsning 14

2I1049 Föreläsning 5. Objektorientering. Objektorientering. Klasserna ordnas i en hierarki som motsvarar deras inbördes ordning

Institutionen för elektro- och informationsteknologi, LTH

Datorteknik. Föreläsning 5. Realtidssystem och realtidsprogrammering. Institutionen för elektro- och informationsteknologi, LTH.

Föreläsning 8 - del 2: Objektorienterad programmering - avancerat

Objektorienterad programmering i Java Undantag Sven-Olof Nyström Uppsala Universitet Skansholm: Kapitel 11

NetBeans 7. Avsikt. Projektfönster

Laboration 1: Figurer i hierarki

Objektinteraktion. Objektorienterad programmering Laboration 2. Syfte Att konstruera ett litet objektorienterat program med flera samverkande objekt.

DAT043 - Föreläsning 7

Operativsystem. Hierarkin för hårdvara läses nerifrån

Idag. Exempel, version 2. Exempel, version 3. Ett lite större exempel

Idag. statiska metoder och variabler. private/public/protected. final, abstrakta klasser, gränssnitt, delegering. wrapper classes

Arbeta med databas. Översikt. Lektion 1: Arbeta med Entity Data Models. Arbeta med Entity Data Models. LINQ (Language Integrated Query).

UML. Översikt UML. Relationer mellan klasser. A är ett aggregerat av B:n. Kontor aggregat av Enheter. 12 olika diagramtyper, bl.a.

Lösningsförslag till tentamen i EDA011, lördagen den 16 december 2006

Subtyping och variance. Objekt-orienterad programmering och design Alex Gerdes, 2018

Tentamen Lösningar EDA698 Realtidssystem

Introduktion till arv

TUTORIAL: SAMLING & KONSOLL

F5: Högnivåprogrammering

Introduktion till Datalogi DD1339. Föreläsning 2 22 sept 2014

Eclipse. Avsikt. Nu ska ett fönster liknande figuren till höger synas.

Laboration 1 - Grunderna för OOP i Java

F5: Högnivåprogrammering

NetBeans 5.5. Avsikt. Projektfönster

TDDD78 Objektorientering: Lagring och livstid

Software Technology. Josef Svenningsson

public och private Obs: private inte skyddar mot access från andra objekt i samma klass.

Teoretisk del. Facit Tentamen TDDC (6)

Objektorienterad programmering i Java Undantag Sven-Olof Nyström Uppsala Universitet Skansholm: Kapitel 11

Föreläsning 3: Händelsestyrda program och användargränssnitt

JAVA Mer om klasser och objektorientering

Subklasser och arv Inledning till grafik (JFrame och JPanel). Något om interface. Objektorienterad programvaruutveckling GU (DIT011) Subklasser

Inledande programmering med C# (1DV402) Tärningarna ska kastas

Editering, Kompilering och Exekvering av Javaprogram

Dagens program. Programmeringsteknik och Matlab. Objektorienterad programmering. Vad är vitsen med att ha både metoder och data i objekten?

Målen med OOSU. Objektorienterad programmering. Objektorienterad programmering. Karlstads Universitet, Johan Öfverberg 1

Datalogi I, grundkurs med Java 10p, 2D4112, Fiktiv tentamen, svar och lösningar och extra kommentarer till vissa uppgifter 1a) Dividera förs

2D1311 Programmeringsteknik för Bio1 och Bio2, vt 2003 Fiktivt prov På flervalsfrågorna är endast ett svar rätt om inget annat anges i frågan! Det rik

Objektorienterad Programkonstruktion. Föreläsning 2 2 nov 2016

Tentamen EDA698 Realtidssystem (Helsingborg)

Javas Exceptions. DD2385 Programutvecklingsteknik Fler bilder till föreläsning 7 23/ Kort om Javas Exceptions Trådar i Java

Tentamen den 18 mars svar Datorteknik, EIT070

Outline. Datorsystemtekni. Kravspecifikation. Kravspecifikation (forts.)

SMD 134 Objektorienterad programmering

Tentamen i Realtidsprogrammering

Transkript:

Trådar i Java Johan Ånäs Seminarieuppsats Institutionen för informationsbehandling Åbo Akademi

Sammandrag I dagens samhälle är vi alla bekanta med att man kan köra flera tjänster och applikationer samtidigt. Detta gör de flesta operativsystem så användarvänliga. Vi kan höra på musik och ladda ner filer från internet samtidigt som vi skriver mail. Denna samtidighet sköts av att operativsystemet låter processorn skifta mellan dessa olika processer i snabb takt. På ett något liknande sätt funkar också trådar i java, dvs ger oss en illusion av att allting sker parallellt. Denna uppsats går ut på att försöka ge läsaren en klarare bild av vad som egentligen händer under den parallelliserade, samspelta ytan. Dessutom bör påpekas att användning av trådar är en oundviklig vital del i Javas struktur. Trådarna för inbyggda javaklasser har oftast dolts på ett så pass förnuftigt sätt att den ovane programmeraren inte kanske ens vet om att han använder sig av dessa lättviktarprocesser eller inte. Trådar bör dock användas med eftertanke eftersom det är väldigt lätt att göra små fel som senare kan vara oerhört svåra att lokalisera. 1

SAMMANDRAG 1 1. INLEDNING 3 2. PSEUDOPARALLELLISM EN ÖVERBLICK 3 2.1 Vad är en tråd? 3 2.2 Jämförelse av trådar och processer 5 2.3 Varför trådar? 6 3. SKAPANDE OCH UPPRÄTTHÅLLANDE AV TRÅDAR 7 3.1 Skapande av trådar 9 3.3 Skedulering av trådar 10 4. SYNKRONISERING AV TRÅDAR 12 4.1 Övervakare och lås 13 4.1.1 Övervakare 13 4.1.2 Låsning av objekt 16 4.2 Synkronisering i praktiken 17 4.3 Felsituationer 20 4.3.1 Dödlägen 20 4.3.2 Trådsvält och svält orsakad av lås 22 5. AVSLUTNING 23 6. REFERENSER 24 2

1. Introduktion Java är ett språk som används över hela världen. Java är tämligen lätt att använda, vilket förstås lockar många nybörjare. Detta gäller inte minst utnyttjandet och implementeringen av pseudoparallellism. Javas virtuella maskin sköter det mesta under huven, vilket en nybörjare antagligen gillar. För somliga kanske det är frustrerande att så mycket döljts för användaren och vi ska i denna uppsats försöka ta oss en titt vad som egentligen händer när Javas virtuella maskin behandlar trådar. Först kommer vi att ta upp vad trådar är och hur de används och sedan behandlar vi lite mera knepiga saker som synkronisation. Synkronisation kan vara ett väldigt svårt kapitel att behärska, till och med vana programmerare måste hålla tungan rätt i mun. Javas virtuella maskin handhar övervakare och lås som vi kommer att se på närmare i kapitlet om synkronisering. Programmeraren behöver egentligen inte förstå dessa saker för att få sitt program att starta, men för att programmet ska fungera korrekt kan det vara värt att se över. Felsituationer uppstår ofta när man arbetar med trådar och att upptäcka fel i ett trådat system kan vara väldigt svårt. 3

2. Pseudoparallellism en överblick. Då man talar om parallellism inom operativsystem menar man ofta program eller så kallade processer som körs samtidigt. Som nybörjare kan man kanske få den bilden att det krävs flera processorer att sköta flera processer, men så är inte fallet. Det går relativt lätt att simulera en dator med en enda processor som om den vore en multiprocessormaskin. Man låter processorn växla körtiden för de olika processerna i en så snabb takt att användaren inte ska märka något, vilket också är målet med så kallad pseudoparallellism. Med körtid menas den tid processen har på sig att utföra sina uppgifter innan den växlar om till nästa väntande process. Trådar och processer är ganska lika till sin natur, men det finns några markanta skillnader som tas up i sektion 2.2. 2.1 Vad är en tråd? Alla programmerare är vana att skriva så kallade sekventiella program. De flesta har troligen någon gång skrivit ett program som matat ut Hello World på skärmen eller något program som räknar primtal eller sorterar namn i en lista. Dessa är sekventiella program, vilket betyder att varje program har ett början, en sekvens och ett slut. Hela programmets gång är en enda sekvens av instruktioner som blir exekverade en efter en, i tur och ordning. En tråd[14] fungerar också på samma sätt som ovan beskrivet. En singel tråd har också en början, en sekvens och ett slut. På ett identiskt sätt som för ett sekventiellt program är hela trådens gång en enda sekvens av instruktioner. En tråd är emellertid inte ett program; en tråd kan inte köra på egen hand. Snarare körs en tråd inom ett program. Figur 2.1.1 illustrerar detta samband. Fig 2.1.1: Singel tråd i ett program [13] Det intressanta med trådar handlar dock inte om körning av en ensam sekventiell tråd. Det fascinerande med trådar är att man kan köra multipler av dem, vilka exekveras parallellt och utför olika arbetsuppgifter i ett program. Fast man har flera trådar än en är de fortsättningsvis sekventiella, men programmet i vilket dom körs kan bli oförutspåbart. Figur 2.1.2 illustrerar multipla trådar i ett program. 4

Fig 2.1.2: Multipla trådar i ett program [14] Eftersom en tråd är ett sekventiellt kontrollflöde måste den också ha egna resurser inom detta exekverande program. T.ex. måste en tråd ha egen stack och programräknare. Koden som körs innanför tråden fungerar bara inom den omgivningen. 2.2 Jämförelse av trådar och processer Processer, eller tungviktarprocesser som de ibland kallas, skapas av kärnan i operativsystemet och är högst oberoende av varandra, dvs processerna delar inte samma minne, vilket trådar (så kallade lättviktarprocesser) gör. Ett belysande exempel på detta kunde vara: - Två processer som undersöker minnesadress 0xFFE84268 ser olika innehåll (värden). - Två trådar som undersöker samma minnesadress kommer att se samma innehåll (värde). Att skapa en process tar relativt länge och att låta processorn byta mellan processer tar hundratals gånger längre än byte mellan trådar. Detta beror just på det att processerna är oberoende av varandra och har egna minnesklumpar. Vid processbyte är detta en stor nackdel, eftersom ett Process kontroll block bör sparas undan i minnet förrän ett byte mellan processer kan ske. Trådar delar däremot på minnesresurserna och registren så stackpekaren och program räknaren (program counter) är det enda vi behöver spara undan. Detta ger trådarna en mycket snabb karaktär. Användar-nivå trådar har alltid den egenskapen att de skapas av processer. Detta innebär att trådar som är skapade i olika processer inte kommer att kunna dela resurser, vilket är förståeligt (i denna uppsats kommer bara användar-nivå trådar att behandlas, men det kan nämnas att det också finns kärn-nivå trådar, vilka operativsystemets kärna skapar). Dessa trådar kan köras parallellt inom en process, ungefär på samma sätt som processer körs parallellt i operativsystemet. Skillnaderna är att trådarna inom en process har delade resurser jämfört med processerna vilka har skilda resurser och att trådarna inte skeduleras på samma sätt som processerna görs. Processer skeduleras med hjälp av en skedulerare i operativsystemet medan trådar skeduleras av programmet. Nedan finns två figurer som beskriver olika minnesaccesseringsmetoder. 5

Fig 2.2.1: Trådar [11] Fig 2.2.2: Processer( modifierad version av [11]) 2.3 Varför trådar? Man kan fråga sig varför man behöver trådar då vi har tillgång till processer. Som svar på detta kan vi utgå från ett exempel: Låt oss säga att vi vill skapa ett ordbehandlingsprogram med vilket man bör kunna arbeta ostört på samma gång som man skriver ut ett dokument. Ordbehandlingsprogrammet bör också innehålla en mekanism som sparar dokumentet automatiskt med jämna mellanrum. För att detta ska fungera i praktiken behöver vi trådar som arbetar på skilda områden. Vi behöver åtminstone en tråd som sköter huvudloopen i programmet och en sedan en tråd som sköter att dokumentet kan skrivas ut. Dessutom kanske vi också har gjort en tråd som sköter presentationen av programmets grafiska utmatning på skärmen. Alla dessa trådar jobbar parallellt vid sidan om varandra. Processer kan däremot inte användas till att jobba gemensamt på detta sätt, åtminstone inte på något vettigt sätt, ty en process är i själva verket programmet som ligger som underlag för dessa trådar. Att försöka få processer att arbeta parallellt kräver att vi kör flera program samtidigt, vilket medför större minnesanvändning samt mer overhead för processorn. Interprocesskommunikation är dessutom långsammare än kommunikation mellan trådar. 6

3. Skapande och upprätthållning av Trådar Att skapa en ny tråd i Java är relativt lätt. Java sköter mycket av det som ligger under ytan. Detta innebär att programmeraren bara behöver förstå grunderna av hur dessa skapas, vad de utför samt hur de uppför sig. Användning av trådar kan snabbt bli väldigt komplext, varför den ovane programmeraren inte rekommenderas börja sin programmeringskarriär med att skriva multitrådbaserade program. Även för experter inom området kan användning av trådar orsaka stort huvudbry. Men som sagt, ibland är användningen av trådar oundviklig och vi ska inom följande sektion ta upp de verkliga grunderna för hur man skapar enkla trådar på användarnivå. 3.1 Skapande av trådar Nedan följer två olika exempel innehållande kod som beskriver hur man startar upp två nya trådar. Dessa exempel har olika struktur, men de kommer att köras på lika villkor. class MyThread extends Thread { MyThread() { public void run() { System.out.println("Jag kör nu och " + Thread.currentThread().getName()); Kodavsnitt 3.1.1: Trådar skapade med hjälp av arv (extends Thread) Följande kod skapar två trådobjekt och startar deras körning. MyThread tr1 = new MyThread( jag är tråd 1 ); tr1.start(); MyThread tr2 = new MyThread( jag är tråd 2 ); tr2.start(); Kodavsnitt 3.1.2: Uppstartning av trådarna för kodavsnitt 3.1.1 class MyThread implements Runnable{ MyThread() { public void run() { System.out.println("Jag kör nu och " + Thread.currentThread().getName()); Kodavsnitt 3.1.3 Trådar skapade användandes gränssnittet Runnable (implements Runnable) På ett liknande sätt skapas och körs även de objekt som använder sig av gränssnittet Runnable. MyThread tr1 = new MyThread( jag är tråd 1 ); new Thread(tr1).start(); MyThread tr2 = new MyThread( jag är tråd 2 ); new Thread(tr2).start(); Kodavsnitt 3.1.4: : Uppstartning av trådarna för kodavsnitt 3.1.3 7

I kodavsnitt 3.1.1 har vi en klass, MyThread, vilken innehåller en metod run som ärvs från klassen Thread. Varje gång en ny tråd av detta objekt skapas enligt kodavsnitt 3.1.2, körs konstruktorn för objektet först. När sedan objektet, i detta fall tr1 eller tr2, externt kallar på start, körs den åsidosatta (overrided) metoden run; Vi har nu startat vår tråd. kodavsnitt 3.1.3 följer i princip detta mönster, bara att vi skapar objekten på ett lite annat sätt. Dessa exempel är skrivna på två olika sätt men bör i teorin ge samma resultat. Men en körning av exemplena ovan kommer troligen inte att ge samma resultat (output), vilket kanske kan komma som en överraskning för en oerfaren programmerare. Märk väl att flera körningar av samma exempel heller inte behöver ge samma resultat. Vi kan exempelvis erhålla resultat där utskriften först är 30 stycken Jag kör nu och jag är tråd 1-skrivningar till skärmen följt av 20 st Jag kör nu och jag är tråd 2-skrivningar fast programmeraren kanske hade tänkt sig att dessa utskrifter skulle alternera. Utskrifterna kan variera från körning till körning. Detta beror inte på att själva programmen i exemplena skulle vara olika, utan på skedulering av trådarna. Körningstid för de olika trådarna kan te sig falsk; man har egentligen ingen kontroll över i vilken ordning dom exekveras om man har två helt fria trådar. Om båda trådarna försöker att accessera ett gemensamt objekt, eller en metod, kan ett så kallat race condition uppstå. Ett race condition är ett högst oönskat läge som kan avvärjas med hjälp av synkronisering, vilket vi kommer att ta upp i kapitlet om synkronisering. Vi har nu två olika metoder att skapa trådar. Frågan är dock vilken som lämpar sig bäst att använda. En tumregel att följa är att om ditt objekt som skall skapas är i behov av att ärva en superclass (en förälderklass) är man tvungen att implementera gränssnittet Runnable. Detta beror på att java inte stöder multipelt arv, dvs man kan inte ärva fler klasser samtidigt. En annan tumregel är att om det trådade objektet som skapas inte åsidosätter (overrides) någon annan metod än run så är Runnable det bästa alternativet. När en tråd startar, kommer den som känt att utföra koden som finns i run metoden. Då tråden når metodens slut kommer den att avslutas. För att hålla en tråd vid liv kan man implementera en loop. För att sedan också kunna avsluta en tråd på ett fint sätt kan man lägga in en flagga i loopen som verifieras med jämna mellanrum. Det finns en infödd metod, stop, vilken avslutar trådens exekveringsstig abrupt. Denna metod bör man man undvika. Metoden stop är väldigt kraftfull och eftersom stop kan genereras från en annan tråd närsomhelst och varsomhelst kan detta få allvarliga konsekvenser. Tråden kanske för tillfället exekverar kod i en kritisk sektion då en annan tråd stoppar den. Detta kunde i värsta fall medföra att semaforer för den kritiska sektionen som tråden ändrat på inte hunnit få sina lock-mekanismer avslagna och medför att sektionens semaforer kommer att blockera alla andra trådar som försöker nå denna sektion. 8

3.2 Livscykel för en tråd Följande figur (fig 3.3.1) visar olika tillstånd som en tråd kan befinna sig i under sin livstid. Figuren beskriver de transitioner som kan antas. Märk väl att detta inte är en fullständig transitionsgraf. Med hälp av figuren ska man istället få överblick av de mest vitala delarna av en tråds liv, ett försök att beskriva livscykeln för en tråd [5]. Fig 3.3.1: transitionsgraf för en tråds livscykel Efter man har skapat en ny tråd med new kommer tråden att vara i tillståndet Ny Tråd. En tråd i detta tillstånd är blott ett tomt Thread objekt; inga systemresurser för tråden har allokerats. När en tråd befinner sig i detta tillstånd kan den endast startas, som transitionsdiagrammet visar. Att anropa någon annan av trådens metoder i detta stadie skulle leda till ett avsteg (exception). När man väl kört start metoden skapas de systemresurser som är nödvändiga för att få en tråd att köra. Då start metoden har returnerat är tråden i tillståndet Exekverar, dvs tråden körs. Men som vi tidigare nämnt; många datorer har endast en processor, vilket gör det omöjligt att köra alla trådar på samma gång. Java runtime system har därför implementerat ett skedulerings schema som fördelar processortid mellan alla trådar. Detta betyder i praktiken att en exekverande tråd kan vänta på sin tur att använda processorn. Vidare om det i sektion 3.4, Skedulering av trådar. En tråd försätts i tillstånd Ej Körbar om något av dessa fall uppstår: Dess sleep metod anropas Tråden kallar på sin wait metod och väntar på att ett visst krav uppfylls Tråden blockeras medan den väntar på I/O För varje inträde till tillståndet Ej körbar, returnerar ett distinkt specifikt exit tråden till Exekverar tillståndet. Ett exit fungerar bara för sitt motsvarande inträde. T.ex. om en tråd har blivit tvingad att sova (genom anropet sleep), måste det angivna antalet millisekunder förflyta innan tråden kan byta till Exekverar igen. Följande lista beskriver exit för varje inträde till tillståndet Ej Körbar. Om en tråd har blivit tvingad att sova, måste det angivna antalet millisekunder förflyta. Om en tråd väntar på att ett krav ska uppfyllas, måste ett annat objekt meddela (notify) den väntande tråden genom att anropa notify. Mera information om detta kommer i kapitel 4, Synkronisering av trådar 9

Om en tråd är blockerad vid I/O, måste I/O först avslutas. Hur ska man då stoppa en tråd helt om man inte får använda metoden stop som nämndes i föregående sektion? Man kunde, som tidigare sagts, använda en flagga som tråden bör checka systematiskt. Vi kommer inte att få en lika snabb avslutning som stop metoden skulle ge men fördelen med att använda en flagga skulle resultera i en mjuklandning; dvs tråden är i ett säkert tillstånd då den läser flaggan som oftast implementerats inne i huvudloopen. Detta betyder i sin tur att tråden inte för tillfället kör i någon kritisk sektion. När tråden stoppats, dvs den har nått run metoden slut, kommer den att försättas i tillståndet Död. Exekveringen av tråden har då avslutats på ett snyggt och regelrätt sätt. 3.3 Skedulering av trådar Som tidigare nämnts så använder sig de flesta datorer av endast en processor (Åtminstone vid tidpunkten då denna uppsats blev skriven). Följaktligen kommer processorn att använda sig av en mekanism kallad skedulering [6]. Skedulering går ut på att försöka fördela processortiden mellan trådar på ett lämpligt sätt. Java runtime omgivningen stöder en väldigt enkel, deterministisk skeduleringsalgoritm som kallas för fixerad prioritetsskedulering (fixed priority scheduling). Denna algoritm skedulerar trådarna på basis av deras prioriteter relativt andra körbara trådar (Runnable threads). När en tråd blivit skapad ärver den prioriteten från sin föräldratråd. Man kan också modifiera prioriteten efter att tråden blivit skapad genom att använda metoden setpriority. Vid en godtycklig tidpunkt, då multipla trådar är redo att bli exekverade, kommer systemet att välja att köra den tråd som har den högsta prioriteten. Endast om denna tråd stoppas, utbyts eller förläggs i tillståndet Ej körbar, kommer en tråd med lägre prioritet att kunna köras. Om flera trådar av samma prioritet väntar på tillgång till processorn, kommer skeduleraren godtyckligt att välja låta någon av dessa trådar köras. Den valda tråden kommer då att exekveras tills något av följande krav uppfylls: En tråd med högre prioritet blir körbar Tråden byts ut, eller om dess run metod når sitt slut På system som stöder tidsfördelning, där dess tilldelning har förfallit Observera dock att tråden med högsta prioritet inte alltid är garanterad att få köra först. Trådskeduleraren kan nämligen välja att köra en lägre prioriterad tråd för att undvika svält (thread starvation). På grund av detta bör man bara använda sig av prioritet för att påverka programmets effektivitet. Man kan inte lita på att skeduleraren ger algoritmisk korrekthet. En så kallad Självisk tråd uppstår om den kör en snäv loop en längre period. Då skeduleraren en gång valt denna tråd, kommer tråden aldrig att avstå från sin makt över processorn. Tråden kommer att fortsätta köra tills loopen naturligt terminerar eller tills en annan tråd med högre prioritet behöver tillgång till processorn. public int tick = 1; public void run() { 10

while (tick < 1000000000) { tick++; Exempel 3.4.1 är kroppen av en typisk självisk tråd. En snäv loop som blir väldigt lång. I vissa fall kommer inte själviska trådar att orsaka problem på grund av att en tråd med högre prioritet alltid tar över den själviska trådens körtid. Men som oftast är fallet kommer dessa själviska trådar att ta över processorn, vilket ofta orsakar att andra trådar måste vänta en lång tid, möjligtvis för alltid, innan de får en chans att bli exekverade. Att skriva processorintensiv kod kan ha negativa konsekvenser. I sådana fall bör man införa trådar som frivilligt kan avstå från processorn med jämna mellanrum för att ge andra trådar en möjlighet att köra. En tråd kan frivilligt lämna ifrån sig processorn genom att anropa metoden yield. Metoden yield ger andra trådar med samma prioritet en chans att köra. Om inga andra trådar med samma prioritet är i tillstånd Körbar, kommer anropet av metoden att att ignoreras. 4. Synkronisering av trådar Trådar är lätta att skapa och ger programmeraren tillgång till den parallellism som behövs i de flesta program. Varför skyr då så många programmerare användandet av trådar? 11

Den största boven i dramat är ännu ännu obehandlad, nämligen synkronisering av trådar [16]. Trådsynkronisering är ofta verkligen klurigt. Varje gång vi skapar fler trådar än en, vilka kommer att dela på samma data, kommer vi att ha potentiella tävlingstillstånd (race conditions). Detta innebär att två eller flera trådar tävlar om en viss resurs, t.ex. en variabel, ett objekt eller en sekvens av kod. Om programmeraren inte vidtar åtgärder kan dessa tävlingstillstånd orsaka stora bekymmer. För att försäkra sig om att datan för en viss applikation kommer att hållas konsistent pga. de ändringar som trådarna gör, behöver man på något sätt försäkra sig om att en tråd inte kan starta en uppdatering av datan förrän en annan tråd som för tillfället skriver till denna data har avslutat. Om man låter detta hända, kommer den gemensamma dataresursen att lämnas åt sitt öde, dvs datan kommer att försättas i ett instabilt tillstånd. Detta innebär att en av dessa trådar eller t.o.m. båda inte kommer att erhålla rätt resultat. Ett tävlingstillstånd kan t.ex. uppstå om vi har två asynkrona trådar som arbetar parallellt. En av trådarna skriver till en fil, medan den andra tråden försöker läsa från filen. Sådana händelser försöker man utesluta genom att införa synkroniserad kod. Genom att, på rätt sätt, synkronisera en funktion eller ett block av kod kan man garantera ensamrätt till funktionen/blocket för den tråd som hinner först. På detta sätt undviks samtidig access anomalier för olika partier av kod och trådarna kommer att uppdateras regelrätt. Utvecklaren bör aldrig låta tävlingstillstånd existera även om det för tillfället inte orsakar problem i programmet. Man kan heller inte garantera att programmet skulle fungera rätt på andra system. 4.1 Övervakare och lås En av Javas trumfer är stödet av multitrådar på användarnivå. Mycket av detta stöd koncentreras på synkronisering. Mekanismerna som Java använder vid stöd av synkronisering kallas för övervakare(monitors)[9] och lås (locks) [10]. 12

4.1.1 Övervakare Med en liten liknelse kan man kort förklara hur en övervakare på ett teoretiskt sätt kunde jämföras med en byggnad; en övervakare är som en byggnad innehållande ett specialrum som kan besittas av endast en tråd åt gången. Rummet kan innehålla data. Tiden från att en tråd träder in i detta rum tills det att den lämnar rummet, har tråden exklusiv tillgång till all data i rummet. Inträdet till övervakarbyggnaden kan jämföras med inträde till övervakaren. Erhållning av det speciella rummet kan jämföras med erhålla övervakaren medan ockupering av rummet på samma sätt jämförs som att äga övervakaren. Att lämna rummet kan ses som att befria övervakaren. Att sedan lämna byggnaden kan jämföras med att träda ut från övervakaren. En övervakare är associerad med en kod av kritisk data som vi kallar för en övervakarregion (monitor region). En övervakarregion är kod som måste exekveras som en icke divisibel operation (atomisk operation)[18] med avseende på en särskild övervakare. Med andra ord måste det för en tråd vara möjligt att exekvera en övervakarregion från början till slut utan att någon annan tråd samtidigt kör en övervakarregion för samma övervakare. Detta har förverkligats och den enda ingången för en tråd till en övervakare är genom att anlända till början av en övervakarregion som är associerad med övervakaren. När en tråd anländer till början av en övervakarregion placeras den i en inträdesmängd för den övervakaren. Inträdesmängden är som en tambur i denna övervakarbyggnad. Om ingen annan tråd väntar i inträdesmängden och heller ej äger övervakaren, kommer tråden att erövra övervakaren och fortsätter att köra sin övervakarregion. När tråden har kört klart träder den ut och frigör övervakaren. Om en tråd anländer till början av en övervakarregion som är skyddad av en övervakare ägd av en annan tråd, kommer den förstnämnde att vänta i inträdesmängden. När den nuvarande ägaren lämnar övervakaren kommer den nyss anlända tråden att tävla med andra trådar som också väntar i inträdesmängden. Bara en av dessa trådar kan vinna tävlingen om att erövra övervakaren. Javas övervakare stöder två typer av trådsynkronisering: ömsesidig uteslutning (mutual exclusion) och samarbete (cooperation). Ömsesidig uteslutning som stöds av Javas virtuella maskin via objektlås (object locks), möjliggör att multipla trådar kan arbeta med delad data utan att interferera med varandra. Samarbete, vilket stöds av JVM via metoderna wait och notify, vilka är integrerade i klassen Object, möjliggör att trådar kan arbeta tillsammans mot ett gemensamt mål. Den första av de olika typerna av synkronisering, ömsesidig uteslutning, hänvisar till den gemensamt uteslutande körningen av övervakarregioner för multipla trådar. Vid vilken tidpunkt som helst kan bara en tråd köra en övervakarregion för en viss övervakare. Vanligtvis är ömsesidig uteslutning endast nödvändig när multipla trådar delar på data eller andra resurser. Om två trådar inte gör det, kommer de vanligtvis inte att interferera med varandra och behöver inte bli exekverade på ett ömsesidigt uteslutande sätt. 13

Den andra typen av synkronisering som stöds av övervakare är samarbete. Medan ömsesidig uteslutning hjälper till att hålla trådar från att samverka med varandra medan de delar data, hjälper samarbete trådar att arbeta tillsammans mot ett gemensamt mål. Samarbete är nödvändigt när en tråd kräver att ett visst data behöver vara i ett visst tillstånd och en annan tråd är ansvarig för att fixa datat till detta tillstånd. T.ex., en tråd, en läsartråd, kanske läser data från en buffer som en annan tråd, en skrivartråd, fyller på. Läsartråden kräver att buffern är i ett icke tomt -tillstånd före den kan läsa data från buffern. Om tråden upptäcker att buffern är tom bör den vänta. Skrivartråden ansvarar för påfyllnad av data till buffern. När väl skrivartråden har utfört skrivande, kan läsartråden läsa igen. Som ett mer praktiskt exempel föreställer vi oss samma scenario med läsartråden och skrivartråden. Antag att buffern är skyddad med hälp av en övervakare. När en läsartråd träder in till övervakaren som skyddar buffen kommer den att kontrollera om buffern är tom. Om buffern inte är tom, läser läsartråden en del data från buffern för att sedan lämna övervakaren. Å andra sidan, om buffern är tom, kommer läsartråden att utföra ett wait kommando och tråden läggs i övervakarens väntemängd (en mängd väntande trådar). Under processen kommer läsartråden att befria övervakaren, som då blir tillgänglig för andra trådar. Vid en senare tidpunkt träder skrivartråden in till övervakaren, skriver en del data till buffern, kör en notify och lämnar övervakaren. När skrivartråden utför en notify blir läsartråden markerad som möjlig för återupplivning. Efter att skrivartråden har lämnat övervakaren kommer läsartråden att bli väckt till liv som ny ägare av övervakaren. Om det finns en chans att någon annan tråd har kommit med in och konsumerat data som lämnats av skrivartråden måste läsartråden explicit kontrollera buffern för att garantera att den inte är tom. Om det inte existerar någon möjlighet att någon annan tråd har konsumerat datan som skrivartråden senast skrev, vet läsartråden att data existerar. Läsartråden läser då en del data från buffern och lämnar sedan övervakaren. Nedan ses en grafisk skildring av övervakare som används av Javas virtuella maskin (Figur 4.1.1). Denna figur beskriver övervakaren som tre rektanglar. I mitten finns en stor rektangel som endast innehåller en tråd, den tråd som för tillfället äger övervakaren. På vänstra sidan har vi en liten rektangel som innehåller inträdesmängden (inträdda trådar). På högra sidan har vi en till liten rektangel som innehåller väntemängden (väntande trådar). Aktiva trådar är cirklar markerade med ett mörkgrått parti medan suspenderade trådar är markerade med ljusgrått. 14

Fig 4.1.1: grafisk skildring av en övervakare [12]. Figur 4.1.1 har också flera numrerade dörrar som trådarna måste träda igenom för att kunna samspela med övervakaren. När en tråd anländer till början av en övervakarregion, stiger den in i övervakaren via dörr 1 och infinner sig i inträdesmängden. Om ingen tråd för närvarande äger övervakaren och ingen tråd finns i väntemängden kommer tråden direkt att forslas genom nästa dörr (dörr 2) och blir ägare till övervakaren. När nu tråden är ägare av övervakaren kommer den att fortsätta att exekvera övervakarregionen. Å andra sidan, om det för tillfället finns en tråd som gör anspråk på övervakaren, måste den senast anlända tråden vänta i inträdesmängden (troligen tillsammans med andra trådar). Den senast anlända blockeras och kommer därför inte att köra övervakarregionen. Figur 4.1.1 visar tre suspenderade trådar i inträdesmängden och fyra trådar suspenderade i väntemängden. Dessa trådar kommer att vänta tills den rådande ägaren av övervakaren den aktiva tråden frigör övervakaren. Den aktiva tråden kan frigöra övervakaren på två sätt: den kan avsluta sin exekvering eller utföra ett wait kommando. Om den fullbordar exekveringen av övervakarregionen kommer den att lämna övervakaren via dörren av den centrala rektangeln i figuren (dörr 5). Om den utför en wait, kommer den att frigöra övervakaren när den stiger ut genom dörr nummer 3 (dörren till väntemängden). Om den tidigare ägaren inte körde en notify när den frigjorde övervakaren, kommer endast trådarna i inträdesmängden att tävla sinsemellan för att erövra övervakaren. Men om den föregående ägaren istället körde en notify, kommer trådarna i inträdesmängden också att tävla med trådarna i väntemängden. Om en tråd ur inträdesmängden vinner tävlingen, kommer den att passera genom dörr nummer 2, för att bli den nya ägaren för övervakaren. Om en tråd ur väntemängden vinner tävlingen, kommer den att lämna väntemängden och återerövra övervakaren när den passerar dörr nummer 4. Notera att dörrarna 3 och 4 är de enda vägarna för en tråd att stiga in till eller lämna väntemängden. En tråd kan endast köra 15

en wait om den för tillfället ägs av övervakaren. Den kan ej heller lämna väntemängden utan att automatiskt först vara ägare av övervakaren. 4.1.2 Låsning av objekt Under körningstid (runtime) är en del av JVM:s dataområden delade av alla trådar medan andra områden är privata för särskilda trådar. Eftersom heapen och metodområdet delas av alla trådar, kommer javaprogram att behöva koordination av multitrådad access till två typer av data: - Instansvariabler, vilka sparas på heapen - Klassvariabler, vilka sparas i metod-området. Program i sig, behöver aldrig koordinera access till lokala variabler, vilka befinner sig på Java-stackar. I den virtuella maskinen är varje objekt och klass logiskt associerade med en övervakare. Ur objektets synpunkt kommer den monitor som är förknippad med ett speciellt objekt att skydda dess instansvariabler. På liknande sätt skyddar en övervakare klassvariablerna för en klass. Om inte objektet innehåller instansvariabler, eller om en klass inte har klassvariabler, har den associerade övervakaren ingenting att skydda. JVM associerar ett lås (lock eller mutex) med varje objekt och klass för att kunna implementera möjligheten av ömsesidig uteslutning för övervakare. Ett lås är ett privilegium som endast en tråd kan äga samtidigt. Trådar behöver inte skaffa sig lås för att accessera en instans- eller klassvariabel. Om en tråd däremot lyckas få tag i ett lås, kommer ingen annan tråd att kunna erhålla samma lås. Om någon annan tråd försöker sig på det, kommer den att vänta tills tråden som låset ägs av frigör det. Att låsa ett objekt går ut på att man erövrar övervakaren som är associerad med objektet. Lås för klasser är implementerande som lås för objekt. När JVM laddar en klassfil, skapas en instans av java.lang.class. När du låser en klass, låser du i själva verket denna klass klass-objekt. En singeltråd tillåts låsa samma objekt flera gånger. För varje objekt upprätthåller JVM en slags räknemekanism som håller reda på hur många gånger objektet har blvit låst. Ett icke låst objekt har en count ekvivalent med 0. När en tråd erövrar låset för första gången kommer denna count att öka till ett. För varje gång tråden erhåller ett lås för samma objekt, kommer count att öka värdet med ett. Men som bör påpekas; endast tråden som redan äger låset för ett objekt kan låsa det ytterligare. Som tidigare nämnts kan ingen annan tråd låsa objektet förrän tråden som äger låset frigör det. Varje gång en tråd frigör låset kommer count at minska med ett. När count når noll, kommer låset att bli befriat och den kritiska sektionen blir tillgänglig för de andra trådarna. En tråd i JVM erhåller ett lås när den kommer till början av en övervakarregion. I Java finns två typer av övervakarregioner: synkroniserade utsagor (synchronized statements) och synkroniserade metoder (synchronized methods). Varje övervakarregion i ett javaprogram 16

är associerad med en objektreferens. När en tråd anländer till första instruktionen i övervakarregionen måste tråden lägga lås på (erhålla låset för) det refererade objektet. Tråden tillåts inte exekvera koden förrän låset för referensen erhållits. När tråden erhåller låset kommer den att få tillträde till blocket innehållande den skyddade koden. När tråden lämnar detta block, på ett sätt eller annat, kommer den att att frigöra låset för det associerade objektet. Den möjligtvis omedvetna javaprogrammeraren kommer aldrig att behöva låsa ett objekt explicit. Detta sköts av javas virtuella maskin. Det som programmeraren egentligen gör, eller det som egentligen sker, är att han identifierar övervakarregionerna av programmet med synkroniserade utsagor och metoder. Då JVM kör programmet kommer den automatiskt att låsa dessa objekt och klasser när den stöter på övervakarregioner. 4.2 Synkronisering i praktiken För att kunna synkronisera ett block av kod bör vi använda oss av nyckelordet synchronized. Vi kan synkronisera vår kritiska sektion med metodsynkronisering eller synkronisering m.h.a. utsagor. Nedan kommer exempel på dessa och deras likheter. public synchronized void minsynkroniserademetod() { //kritisk sektion av data Kodavsnitt 4.2.1: metodsynkronisering Kodavsnitt 4.2.1 är ett kort exempel på hur man gör för att synkronisera en metod. public void minsynkroniserademetod() { synchronized (this){ //kritisk sektion av data Kodavsnitt 4.2.2: synkronisering m.h.a utsaga Kodavsnitt 4.2.2 är ekvivalent med kodavsnitt 4.2.1. Det är bara syntaktiskt som dessa exempel kan skiljas åt. Man har dock större möjlighet att påverka programmets snabbhet om man synkroniserar enskilda delar av koden istället för att synkronisera en hel metod (om det alltså finns en sådan möjlighet). Att synkronisera en hel metod kanske inte alltid är så lämpligt, särskilt inte om metoden är stor och kanske endast en liten del av koden behöver uteslutas från samtidig access. Att man låser den kritiska sektionen till referensen this i kodavsnitt 4.2.2 betyder i praktiken att man låser den kritiska sektionen som associerar till det objekt som för tillfället är aktivt inom denna sektion. Detta kan lättare belysas med ett exempel. 17

Låt oss säga att vi har en klass som man utgår från och att denna klass har ärvt Thread (extends Thread), samt innehåller en åsidosatt run metod och en synkroniserad metod synkfunk. Vi skapar tre trådar från detta objekt: a,b och c, enligt följande mönster: a = new Thread( this ), Mitt namn är a ); a.start(); Kodavsnitt 4.2.3: skapande av tråd På samma sätt skapas och körs också b och c. När man startat en tråd söks run-metoden upp och exekveras. I det tänkta exemplet finns en oändlig loop i run-metoden. Det kommer nu att existera tre trådar som turas om att exekvera samma objekt. Loopen innehåller dessutom ett anrop till den synkroniserade metoden synkfunk (enligt synkronisering m.h.a. utsaga), som ser ut som nedan. public synkfunk(){ synchronized (this){ //motsvarar en synkroniserad metod for(int i=0; i<100; i++) { System.out.println(Thread.currentThread().getName()); Kodavsnitt 4.2.4: metoden synkfunk, låser den kritiska sektionen med avseende på this. Varje gång någon av trådarna i loopen kallar på denna metod kommer tråden i fråga att stiga in i denna metod. Är det synkroniserade blocket inte är låst med avseende på this, kommer tråden att erhålla monitorn för detta objekt (sitt eget) för att sedan stiga in till den kritiska sektionen. Tråden har nu ensamrätt till den kritiska sektionen. MEN, denna ensamrätt gäller bara om vi vet att resten av trådarna också exekverar samma objekt. Man kan alltså inte utesluta att andra objekt når denna kritiska sektion om man bara låser sektionen med avseende på this. Som exempel kan man skapa tre stycken objekt (a, b och c) och alla dessa objekt bör förstås tillhöra samma klass. Denna klass ärver Thread och innehåller en åsidosatt run-metod. Alla tre objekt skapas enligt kodavsnitt 4.2.5 nedan. objekta = new mitttradobjekt(); Kodavsnitt 4.2.4: skapande av objekt I konstruktorn för mitttradobjek skapar vi ett nytt objekt, synkroniseratobjekt, vilket innehåller en metod synkfunk (nästan samma som i föregående exempel, bara att vi inte låser sektionen med avseende på this). Trådarna a, b och c skapas och startas enligt följande mönster (kodavsnitt 4.2.4 visar bara a, men b och c skapas på samma sätt): a = new Thread( objekta ), Mitt namn är a ); a.start(); Kodavsnitt 4.2.4: skapande av tråd Vi har nu ett skilt objekt med en egen tråd. Varje tråd kör en egen loop till skillnad från förra exemplet (där alla exekverade loopen för samma objekt). Lite likt föregående exempel har vi en funktion, synkfunk, men som i detta fall hör till klassen mitttradobjekt. 18

public synkfunk(){ synchronized ( minsemafor ){ //motsvarar en synkroniserad metod for(int i=0; i<100; i++) { System.out.println(Thread.currentThread().getName()); Kodavsnitt 4.2.4: metoden synkfunk, låser kritiska sektionen med avseende på ett externt objekt, minsemafor. Märk att referensen this har bytts ut mot ett objekt (eller referens) minsemafor. Varje gång en tråd stiger in i synkfunk och når den kritiska sektionen, kommer den att kolla om objektlåset är öppet. I detta fall måste man använda sig av något annat objekt än vad this refererar till. Det behövs alltså ett gemensamt objekt som alla trådade objekt känner till. På detta sätt kan man synkronisera den kritiska sektionen korrekt. Vi ska ännu ta upp ett exempel på det vi beskrev i sektion 2.1.2, dvs hur det fungerar om en tråd lägger flera lås på samma objekt. class MinKlass { //flera locks på samma objekt public synchronized void metod1() { metod2(); System.out.println("jag är nu i metod1()"); public synchronized void metod2() { System.out.println("nu är jag i metod2()"); Kodavsnitt 4.2.5: återtlåsning I kodavsnitt 4.2.5 finns en klass MinKlass som innehåller två metoder metod1 och metod2. När den synkroniserade metoden metod1 exekveras kommer den att erhålla ett lås på sitt eget objekt. Sedan kallar den på metod metod2 som också kräver ett lås på det egna objektet. Om Java inte tillät trådar att återerövra sina egna lås skulle tråden i metod1 låsas i utanför metod2 och vara oförmögen att fortsätta. Eftersom metod1 väntar på att metod2 ska öppna objektlåset får vi ett så kallat dödläge (deadlock), vilket vi kommer att ta upp i nästa kapitel. Som tur är, tillåter JVM att ett lås kan bli låst flera gånger och koden ovan kommer att kompileras och köras utan problem. 19

4.3 Felsituationer När man arbetar med trådar är det väldigt lätt att göra snedsteg. Det gäller att synkronisera sina funktioner och kodavsnitt för att allting ska fungera rätt. Nedan kommer exempel på några vanliga felsituationer som kan uppstå då man kodar och kör felaktiga program. 4.3.1 Dödlägen Man säger att en mängd trådar (eller processer) är i dödläge[17] när varje tråd i mängden väntar på en händelse (event) som bara någon av dessa trådar i mängden kan orsaka. Om man upptäcker att trådarna väntar på varandra cirkulärt utan att något händer får vi ett dödläge. Ett exempel på detta kan illustreras som en korsning med fyra bilar. Varje bil väntar på att den bil som finns på högra sidan skall åka. Fig 2.1.1: Dödläge med bilar [15] Figur 4.3.1 beskriver denna situation. Enda sättet att lösa problemet för dessa bilar vore att någon backar eller att någon bil plockas bort. Dödlägen kan uppstå i Java eftersom nyckelordet synchronized orsakar den exekverande tråden att blockera medan den väntar på låset (eller rättare sagt övervakaren) som är associerad med det specifika objektet. Eftersom tråden möjligtvis redan innehar ett lås associerad med något annat objekt, kan en sådan situation uppstå att två (eller flera) trådar väntar på att den förstnämnde tråden ska befria sitt lås. I ett sådant fall kommer dessa trådar att vänta för evigt. Följande exempel som illustreras nedan har en mängd metoder som potentiellt kunde medföra ett dödläge. Båda metoderna förvärvar lås för två objekt, semafor1 och semafor2, förrän de fortsätter. Objekten i detta exempel fungerar som lås och är globala (statiska), vilket är en ganska vanlig teknik för att underlätta arbetet. 20

public static Object semafor1 = new Object(); public static Object semafor2 = new Object();... public void metod1() { synchronized (semafor1) { synchronized (semafor2) { //gör någonting public void metod2() { synchronized (semafor2) { synchronized (semafor1) { //gör någonting annat Kodavsnitt 4.3.1: potentiella dödlägen Om tråd A anropar metod1 samtidigt som tråd B anropar metod2 och om tråd A erövrar låset för semafor1 samtidigt aom tråd B erövrar semafor2 kommer trådarna att försättas i ett dödläge: ingen av trådarna vill ge upp sitt lås förrän den har fått tag på det andra låset. Ingen av trådarna kommer heller att kunna erövra det andra låset förrän den andra tråden låser upp det. När ett javaprogram hamnar i ett dödläge kommer trådarna att vänta för evigt. Enda man kan göra (om man inte ändrar koden), är att starta om programmet och hoppas att programmet inte hamnar i ett dödläge igen. Detta är en dålig nödlösning, men som ofta tillämpas. Att testa och försöka hitta dödlägen är väldigt svårt eftersom dödlägen beror på timing, datorns interna lägen och omgivningen. Dödlägen kan uppstå väldigt sällan eller bara under vissa omständigheter. Kod kan ha potential för dödlägen, som kodavsnitt 4.3.1, men uppvisar inte dödlägen förrän någon oturlig kombination av slumpmässiga och icke slumpmässiga händelser inträffar. Dödlägen påminner om tickande tidsbomber som väntar på att explodera. Turligt nog kan man utnyttja ett relativt lätt krav på förvärvandet av lås, som kan skydda oss mot dödlägen för synkronisering. Metoderna i föregående kodavsnitt har potential för dödlägen eftersom varje metod erövrar dessa två lås i olika ordning. Om kodavsnitt 4.3.1 hade blivit skrivet så att varje metod erövrar dessa lås i samma ordning, skulle två eller flera trådar som exekverar dessa metoder inte falla i dödläge. Detta beror på att en tråd i såna fall inte kan erövra det andra låset om den inte äger det första. Om man kan garantera att lås alltid blir erövrade i en följdriktig ordning, kommer inget dödläge att uppstå. 21

4.3.2 Trådsvält och svält orsakad av lås Svält uppstår när JVM inte kan allokera tid för en tråd som bör exekveras. Detta kan t.ex. bero på en dålig skeduleringsalgoritm, dålig programmeringsstil (t.ex. att man inte återvänder från en paintmetod i en applet), eller en fientlig attack, t.ex. vid denial of service attack mot en värd (host) där processorn kommer att vara upptagen utanför Java processen) En av de vanligaste orsakerna till svält är att man valt en dålig strategi gällande prioriteter för trådarna. Om någon av trådarna har hög prioritet, är väldigt upptagen och aldrig blockeras kommer den att svälta ut trådarna med lägre prioritet. För att undvika trådsvält har vi en användbar tumregel: ge sådana trådar som har mycket att göra en lägre prioritet; och ge trådar som har lite uppgifter att uträtta högre prioritet. På detta sätt kommer den högt prioriterade tråden att vakna upp, utföra sin sak, och återvända till sömn. Detta kommer att ge trådarna med låg prioritet tid att utföra sina uppgifter. Svält är ofta ett skeduleringsproblem som uppstår på grund av att skeduleraren inte ökar prioritet för trådar som inte har fått använda CPU:n. Svält orsakad av lås uppstår när en tråd får exekveringstid, men aldrig lyckas erövra ett lås på grund av att en annan tråd äger låset för tillfället. Svält orsakad av lås är dock en ovanlig företeelse. Nedan är några punkter, vilka alla bör gälla för att en sådan svält skall kunna uppstå. Fler än en tråd måste försöka att erövra samma lås De mellanliggande resultaten under striden av erhållande av lås är av intresse. Trådarna bör ha samma prioritet En round-robin skedulerare bör användas. svält orsakad av lås uppstår eftersom: Förvärv av lås inte köas Om en tråd försöker att erövra ett lås, kommer den inte att kontrollera om en annan tråd tidigare har försökt att erövra låset och misslyckats. När ett lås har frigjorts, kan tråden som för tillfället exekveras välja att fortsätta vara den som exekverar. Programmeraren bör alltid testa sitt program och se hur det uppför sig. Trådsvält är lättare att spåra än dödlägen som ligger potentiellt gömda i koden. 22

5. Avslutning Med denna uppsats har jag försökt belysa användningen av trådar ur en mera teoretisk synvinkel för att läsaren ska få en bättre helhetsbild av vad som sker i Javas virtuella maskin. Syftet med uppsatsen är inte att försöka tvinga folk att mekaniskt lära sig programmera, utan snarare ge dem en spekulativ snabbskolning. Denna uppsats skall heller inte läsas som om den vore en manual. Många av kodavsnitten kan kanske kännas osammanhängande och det är inte meningen att läsaren ska försöka utveckla program baserade på den använda koden. Vi har dock lärt oss grunderna i hur man skapar och använder trådar. Vi har koncist ögnat igenom vissa sektioner, till exempel hur man praktiskt använder trådar, medan vi har fördjupat oss i andra, såsom sektionen om övervakare och lås. Jag anser att det är viktigt att läsaren får en viss förståelse av mekanismen bakom trådar eftersom användningen är väldigt generell. Jag försöker också vinkla uppsatsen och göra den intressantare än en fadd manual. Som vi kanske har märkt är konceptet med trådar inte allt för svårt att förstå, men tyvärr är det oftast implementeringen och användning av trådarna som är den stora utmaningen. Som tidigare sagts, sköter java mycket av trådarnas egenskaper under ytan. Detta leder ofta till att den ovane programmeraren skriver dålig, okontrollerad kod. Själv har jag inte sysslat med trådar allt för länge, vilket medför att jag också lärt mig en del under detta arbete. Varför jag behandlar trådar i Java beror på att jag vill utöka mina kunskaper och få mer erfarenhet: jag programmerar på ett projekt angående ett strategibaserat nätverksspel. Som vi alla känner till vid det här laget, blir ingen glad av ett program som låser sig eller inte funkar som det ska. 23

6. Referenser (1) Java Threads, 2nd edition (Scott Oaks and Henry Wong ) (2) Writing multithreaded Java applications www-128.ibm.com/developerworks/library/j-thread.html, Alex roetter, Software engineer, Teton Data Systems (3) java.sun.com/docs/books/tutorial/essential/threads/ (Sun Microsystems) (4) java.sun.com/docs/books/tutorial/essential/threads/definition.html (Sun Microsystems) (5) java.sun.com/docs/books/tutorial/essential/threads/lifecycle.html (Sun Microsystems) och Threads Primer A Guide to Multithreaded Programming, Bil Lewis, Daniel J. Berg, kap 4 - sid 69 (6) java.sun.com/docs/books/tutorial/essential/threads/priority.html (Sun Microsystems) (7) Java Threads, 2nd edition (Scott Oaks and Henry Wong ), sid 36 (8) How the Java virtual machine performs thread synchronization (Bill Venners) www.javaworld.com/javaworld/jw-07-1997/jw-07-hood.html (9) Thread synchronization (Bill Venners) www.artima.com/insidejvm/ed2/threadsynch.html (10) Object Locking (Bill Venners) www.artima.com/insidejvm/ed2/threadsynch2.html (11) www.javaworld.com/javaworld/jw-09-1998/images/nt_threads.gif (12) www.artima.com/insidejvm/ed2/images/fig20-1.gif (13) java.sun.com/docs/books/tutorial/figures/essential/threadssingle.gif (14) java.sun.com/docs/books/tutorial/figures/essential/threads-two.gif (15) www.cs.fsu.edu/~baker/cop5611.s03/graphics/f6-1.jpg (16) Multithreaded Programming with JAVAtm Technology, Bil Lewis, Daniel J. Berg, kap.6 sid 73 (17) Pthreads Primers A Guide to Multithreaded Programming, Bil Lewis, Daniel J. Berg, kap 7 sid 144 24

(18) Multithreaded Programming with JAVAtm Technology, Bil Lewis, Daniel J. Berg, kap.6 sid 74 25