Projekti 1 Säikeet ja kriittisen vaiheen kontrollointi javalla

Samankaltaiset tiedostot
Rinnakkaisohjelmointi kurssi. Opintopiiri työskentelyn raportti

Luento 6. T Ohjelmoinnin jatkokurssi T1 & T Ohjelmoinnin jatkokurssi L1. Luennoitsija: Otto Seppälä

Monitorit -projekti Rinnakkaisohjelmointi

Liite 1. Projektin tulokset (Semaforit Javassa) Jukka Hyvärinen Aleksanteri Aaltonen

Rinnakkaisohjelmointi, Syksy 2006

Ohjelmoinnin peruskurssien laaja oppimäärä

Javan semaforit. Joel Rybicki, Aleksi Nur mi, Jara Uitto. Helsingin yliopisto

Rajapinta (interface)

Ohjelmointi 2 / 2010 Välikoe / 26.3

HOJ Säikeet (Java) Ville Leppänen. HOJ, c Ville Leppänen, IT, Turun yliopisto, 2012 p.1/55

T Henkilökohtainen harjoitus: FASTAXON

RINNAKKAINEN OHJELMOINTI A,

14. Poikkeukset 14.1

Java kahdessa tunnissa. Jyry Suvilehto

10 Lock Lock-lause

Tehtävä 1. Tehtävä 2. Arvosteluperusteet Koherentti selitys Koherentti esimerkki

11. Javan toistorakenteet 11.1

Luokan sisällä on lista

Sisällys. 14. Poikkeukset. Johdanto. Johdanto

Olio-ohjelmointi Javalla

812315A Ohjelmiston rakentaminen. Asynkronisuus

14. Poikkeukset 14.1

Sisällys. 14. Poikkeukset. Johdanto. Johdanto

Lohkot. if (ehto1) { if (ehto2) { lause 1;... lause n; } } else { lause 1;... lause m; } 16.3

12. Javan toistorakenteet 12.1

Jaana Diakite Projekti 1 JAVA-Monitorit 1(13) Rinnakkaisohjelmointi Anu Uusitalo

Kompositio. Mikä komposition on? Kompositio vs. yhteyssuhde Kompositio Javalla Konstruktorit set-ja get-metodit tostring-metodi Pääohjelma

Harjoitus Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti:

Luokka Murtoluku uudelleen. Kirjoitetaan luokka Murtoluku uudelleen niin, että murtolukujen sieventäminen on mahdollista.

Metodien tekeminen Javalla

9. Periytyminen Javassa 9.1

Semaforit Javassa. Mari Kononow, Eveliina Mattila, Sindi Poikolainen HELSINGIN YLIOPISTO

Poikkeustenkäsittely

12. Javan toistorakenteet 12.1

Listarakenne (ArrayList-luokka)

Ohjelmointi 2 / 2008 Välikoe / Pöytätestaa seuraava ohjelma.

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Taulukot & Periytyminen

Mikä yhteyssuhde on?

5/20: Algoritmirakenteita III

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä. Tiedonkätkentä. Aksessorit. 4.2

Olio-ohjelmointi Virhetilanteiden käsittely

Ohjelmoinnin perusteet, kurssikoe

1 Tehtävän kuvaus ja analysointi

812347A Olio-ohjelmointi, 2015 syksy 2. vsk. X Poikkeusten käsittelystä

Vertailulauseet. Ehtolausekkeet. Vertailulauseet. Vertailulauseet. if-lauseke. if-lauseke. Javan perusteet 2004

JAVA on ohjelmointikieli, mikä on kieliopiltaan hyvin samankaltainen, jopa identtinen mm. C++

Ohjelmoinnin jatkokurssi, kurssikoe

9. Periytyminen Javassa 9.1

812341A Olio-ohjelmointi Peruskäsitteet jatkoa

Metodit. Metodien määrittely. Metodin parametrit ja paluuarvo. Metodien suorittaminen eli kutsuminen. Metodien kuormittaminen

Sisällys. Yleistä attribuuteista. Näkyvyys luokan sisällä ja ulkopuolelta. Attribuuttien arvojen käsittely aksessoreilla. 4.2

Javan perusteita. Janne Käki

8. Näppäimistöltä lukeminen 8.1

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op. Poikkeukset ja tietovirrat: Virhetilanteiden ja syötevirtojen käsittely

815338A Ohjelmointikielten periaatteet

Pino S on abstrakti tietotyyppi, jolla on ainakin perusmetodit:

1. Mitä tehdään ensiksi?

Concurrency - Rinnakkaisuus. Group: 9 Joni Laine Juho Vähätalo

JAVA-OHJELMOINTI 3 op A274615

Lohkot. if (ehto1) { if (ehto2) { lause 1;... lause n; } } else { lause 1;... lause m; } 15.3

Java-API, rajapinnat, poikkeukset, UML,...

1. Kun käyttäjä antaa nollan, niin ei tulosteta enää tuloa 2. Hyväksy käyttäjältä luku vain joltain tietyltä väliltä (esim tai )

Ohjelmointityö 3. Mikko Laamanen

public static void main (String [] args)

Ohjelmassa henkilön etunimi ja sukunimi luetaan kahteen muuttujaan seuraavasti:

Periytyminen (inheritance)

Sisällys. 11. Javan toistorakenteet. Laskurimuuttujat. Yleistä

Monitorit. Monitori Synkronointimenetelmiä Esimerkkejä. Andrews , Stallings 5.5

Monitorit. Tavoite. Monitori Synkronointimenetelmiä Esimerkkejä. Andrews , Stallings 5.5. Minimoi virhemahdollisuuksia

JAVA-PERUSTEET. JAVA-OHJELMOINTI 3op A JAVAN PERUSTEET LYHYT KERTAUS JAVAN OMINAISUUKSISTA JAVAN OMINAISUUKSIA. Java vs. C++?

A) on käytännöllinen ohjelmointitekniikka. = laajennetaan aikaisemmin tehtyjä luokkia (uudelleenkäytettävyys)

Yleistä. Nyt käsitellään vain taulukko (array), joka on saman tyyppisten muuttujien eli alkioiden (element) kokoelma.

Luokat ja oliot. Ville Sundberg

Informaatioteknologian laitos Olio-ohjelmoinnin perusteet / Salo

private TreeMap<String, Opiskelija> nimella; private TreeMap<String, Opiskelija> numerolla;

Sisältö. 22. Taulukot. Yleistä. Yleistä

1. Omat operaatiot 1.1

Oliosuunnitteluesimerkki: Yrityksen palkanlaskentajärjestelmä

8. Näppäimistöltä lukeminen 8.1

Sisältö. 2. Taulukot. Yleistä. Yleistä

Sisällys. 12. Näppäimistöltä lukeminen. Yleistä. Yleistä

1.3 Lohkorakenne muodostetaan käyttämällä a) puolipistettä b) aaltosulkeita c) BEGIN ja END lausekkeita d) sisennystä

812341A Olio-ohjelmointi, IX Olioiden välisistä yhteyksistä

4. Luokan testaus ja käyttö olion kautta 4.1

11. Javan valintarakenteet 11.1

7. Oliot ja viitteet 7.1

Ohjelmointitaito (ict1td002, 12 op) Kevät Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen

13. Loogiset operaatiot 13.1

12. Näppäimistöltä lukeminen 12.1

Taulukot. Jukka Harju, Jukka Juslin

Tietorakenteet (syksy 2013)

Ohjelmointi 1 / 2009 syksy Tentti / 18.12

Olion elinikä. Olion luominen. Olion tuhoutuminen. Olion tuhoutuminen. Kissa rontti = null; rontti = new Kissa();

Rinnakkaisuus (.NET) Juha Järvensivu 2007

58131 Tietorakenteet ja algoritmit (syksy 2015)

ITKP102 Ohjelmointi 1 (6 op)

Ohjelmointi 2 / 2011 Välikoe / 25.3

Sisältö Johdanto. Tiedostojen lukeminen. Tiedostojen kirjoittaminen. 26.2

Johdatus Java 1.5:n semaforeihin

1.3Lohkorakenne muodostetaan käyttämällä a) puolipistettä b) aaltosulkeita c) BEGIN ja END lausekkeita d) sisennystä

Transkriptio:

Projekti 1 Säikeet ja kriittisen vaiheen kontrollointi javalla Lasse Leino ja Marko Kahilakoski Helsingin Yliopisto Tietojenkäsittelytieteen laitos Rinnakkaisohjelmointi 18. joulukuuta 2006

Sisältö 1 Säikeet ja kriittisen vaiheen kontrollointi javalla 1 1.1 Yleistä................................. 1 1.2 Keskeytykset............................. 2 1.3 Säikeen odottaminen......................... 3 1.4 Synkronisointi............................. 3 1.5 Producer-Consumer......................... 6 1.5.1 Producer........................... 6 1.5.2 Consumer........................... 7 1.5.3 Storage............................ 8 1.5.4 Producer-Consumer test................... 11 i

1 Säikeet ja kriittisen vaiheen kontrollointi javalla 1.1 Yleistä Javassa kaikilla ohjelmilla on yksi säie, eli pääohjelma (main thread), jossa pystytään luomaan uusia säikeitä. Tämä voidaan toteuttaa kahdella tavalla. Rajapintaluokka Runnable määrittelee metodin run(). Luokka voidaan laittaa toteuttamaan rajapintaluokka Runnable, jonka jälkeen tehdään luokkaan metodi run(), joka sisältää säikeen ohjelmakoodin. Tämän jälkeen Runnableolio annetaan parametrina Thread:in konstruktorille. Esimerkiksi: public class Kivaa implements Runnable { public void run() { System.out.println("Rio on kivaa!"); public static void main(string args[]) { (new Thread(new Kivaa())).start(); Toinen vaihtoehto on tehdä luokasta, Thread:in aliluokka. Thread toteuttaa itse rajapintaluokan Runnable, mutta sen run-metodi on tyhjä. Run-metodi toteutetaan taas itse luokassa. Esimerkiksi: public class Kivaa extends Thread { public void run() { System.out.println("Rio on kivaa!"); public static void main(string args[]) { (new Kivaa()).start(); Molemmissa tapauksissa säie käynnistetään metodilla Thread.start. Ensimmäinen tapa säikeiden tekoon on yleisempi ja joustavampi, koska luokka voi samalla toimia jonkun muun kuin Thread-luokan aliluokkana. 1

1.2 Keskeytykset Säikeen suoritus voidaan asettaa suspend-tilaan metodilla Thread.sleep. Sleep ottaa parametreinaan taukoajan joko milli- tai nanosekunteina. Ei voida olla kuitenkaan varmoja, että säikeen suoritus keskeytyy juuri annetuksi ajaksi, koska toiminnon tarkkuus riippuu käyttöjärjestelmästä, jolla ohjelmaa ajetaan. Seuraava esimerkki tulostaa annetun tekstin kymmenen kertaa yhden sekunnin välein. public class Kivaa { public static void main(string args[]) throws InterruptedException { for (int i = 0; i < 10; i++) { Thread.sleep(1000); System.out.println("Rio on kivaa!"); Sleep-metodi heittää poikkeuksen InterruptedException, jos jokin toinen säie keskeyttää nukkuvan säikeen. Jos halutaan tehdä jotain erityistä, kun säie keskeytetään, käsitellään InterruptedException poikkeus esimerkiksi seuraavalla tavalla: public class Kivaa { public static void main(string args[]) { for (int i = 0; i < 10; i++) { try { Thread.sleep(1000); catch (InterruptedException e) { System.out.println("Keskeytys :I"); return; System.out.println("Rio on kivaa!"); Jos säie tekee paljon töitä (vaikka numeronmurskausta) eikä käynnistä yhtään metodia, joka heittäisi poikkeuksen InterruptedException, voidaan tarkistaa myös itse onko keskeytystä tullut. Tämä tehdään Thread.interrupted metodia käyttäen. Esimerkiksi: if (Thread.interrupted()) { throw new InterruptedException(); Kun säie keskeytetään, sille asetetaan keskeytystila, joka kertoo, että säie on keskeytetty. Tämä poistuu, kun käytetään edeltävää metodia. Jos halutaan vain tarkistaa keskeytyksen tila, voidaan käyttää metodia Thread.isInterrupted, joka 2

jättää keskeytystilan ennalleen. 1.3 Säikeen odottaminen Join-metodi mahdollistaa tilanteen, jossa säikeen on odotettava toisen säikeen suorituksen valmistumista. Parametrina voi antaa myös ennalta määritellyn odotusajan, kuten Sleep-metodilla, mutta odotusaikojen luotettavuudessa on samat ongelmat. Esimerkiksi: säie odottaa toisen säikeen valmistumista. toinensäie.joins(); 1.4 Synkronisointi Esitetään yksikertainen esimerkki laskurista, jolla voi joko lisätä lukua yhdellä tai vähentää sitä yhdellä. Esimerkiksi: public class Laskuri { private int luku = 0; public void lisää() { luku++; public void poista() { luku--; public int arvo() { return luku; Ongelmia seuraa, jos meillä on monta säiettä suorittamassa samaa koodia, koska ne voivat mennä kriittisesti päällekäin. Esimerkiksi komento luku++ voidaan jakaa kolmeen osaan: haetaan luvun nykyinen arvo, lisätään luvun arvoa yhdellä ja talletetaan luvun arvoksi saatu arvo. Seuraava skenaario on siis mahdollinen: Säie A hakee luvun nykyisen arvon (luku = 0) Säie B hakee luvun nykyisen arvon (luku = 0) Säie A lisää arvoa yhdellä (luku = 1) Säie B lisää arvoa yhdellä (luku = 1) 3

Säie A tallentaa uuden arvon (luku = 1) Säie B tallentaa uuden arvon (luku = 1) Edellinen skenaario on siis virheellinen, koska luvun arvon pitäisi kahden lisäyksen jälkeen olla kaksi. Ongelma poistuu, kun käytetään synkronoituja metodeja. Muutetaan esimerkkiä: public class Laskuri { private int luku = 0; public synchronized void lisää() { luku++; public synchronized void poista() { luku--; public synchronized int arvo() { return luku; Kun säie A on suorittamassa synkronoitua metodia lisää, muut säikeet, jotka haluavat suorittaa saman metodin joutuvat odottamaan vuoroaan. Nyt edellisen esimerkin ongelmaa ei enää ole. Javassa tälläinen lukko on toteutettu monitoreilla. Synkronoidun metodin lukko varaa metodin olion ja kun metodin suoritus päättyy, lukko vapautuu. Jos on tilanne, jossa kasvatetaan kahta lukua, mutta niiden kasvattaminen ei riipu ollenkaan toisistaan, niin synkronoidun metodin lukko varaisi yhden säikeen suorituskerralla koko luokan itselleen ja estäisi näin lukujen yhtäaikaisen kasvattamisen. Tähän voidaan käyttää synkronoituja lauseita, joissa täytyy antaa parametrina, mitä oliota käytetään lukkona. Esimerkiksi: public class toinenlaskuri { private long luku = 0; private long toinenluku = 0; private Object lukko = new Object(); private Object toinenlukko = new Object(); public void kasvataensimmäistä() { synchronized(lukko) { luku++; 4

public void kasvatatoista() { synchronized(toinenlukko) { toinenluku++; Nyt molempien lukujen kasvattaminen on säieturvallista ja niitä voidaan kasvattaa keskenään rinnakkain. Metodi joka odottaa tietyn ehdon täyttymistä ennen kuin se jatkaa suoritustaan voidaan laittaa odottamaan metodilla Object.wait. Säie ei jatka suoritustaan ennen kuin jokin toinen säie antaa sille keskeytyksen. Huomaa, että tämä keskeytys ei välttämättä ole se mitä metodi odottaa, joten pitää tarkistaa, täyttyykö haluttu ehto. Esimerkiksi: public synchronized odota() { while (odota) { try { wait(); catch (InterruptedException e) { Kun säie suorittaa metodin wait(), se luopuu lukostaan ja siirtyy suspendtilaan ja jää odottamaan. Kaikille lukkoa odottaville säikeille voidaan ilmoittaa muutoksesta: public synchronized lopetaodotus() { odota = false; notifyall(); Jos halutaan herättää vain yksi säie, voidaan käyttää metodia notify. Esitellään vielä lopuksi kurssilla tutuksi tullut Producer-Consumer ongelma toteuttettuna javalla seuraavassa kappaleessa. 5

1.5 Producer-Consumer 1.5.1 Producer Tuottajaluokka, Thread luokan aliluokka * public class Producer extends Thread { private Storage storage; private int number; private int puts = 0; // Uniikki kuluttajanumero // Tuottokerrat * Konstruktori, asettaa tuottajan Storageen * @param c * @param number public Producer(Storage c, int number) { storage = c; storage.addproducers(); this.number = number; System.out.format("Created producer #%d\n", number); * Threadin ajometodi. * Ajetaan kunnes suoritusaika saavutetaan, tai kunnes viimeinenkin Consumer on * lopettanut public void run() { do { int temp = (int)(math.random() * 1000); // Tuotettava elementti System.out.format("Producer %d trying to put %d. %d consumers active...\n", number, temp, storage.getnumconsumers()); storage.put(number, temp); // Asetetaan elementti Storageen puts++; // Kasvatetaan tuottoja try { sleep((int)(math.random() * 1000)); // Koetetaan nukkua (järkevästi) // satunnainen aika catch (InterruptedException e) { // Saadaan keskeytys, voidaan jatkaa while (storage.getfinishtime() > System.currentTimeMillis() && storage.getnumconsumers() > 0); storage.retireproducers(); // Jäädään eläkkeelle. System.out.format("Producer #%d retiring. Made %d put operations...\n", number, puts); 6

1.5.2 Consumer Kuluttajaluokka, Thread luokan aliluokka * public class Consumer extends Thread { private Storage storage; private int number; // Uniikki kuluttajanumero private int gets = 0; // Hakukerrat public Consumer(Storage c, int number) { // Kuluttajakonstruktori storage = c; storage.addconsumers(); // Lisätään kuluttajien yhteismäärää this.number = number; System.out.format("Created consumer #%d\n", number); public void run() { // Threadin ajometodi int value = 0; do { // Loopataan, kunnes suoritusaika saavutetaan, tai kunnes viimeinenkin // Tuottaja on lopettanut value = storage.get(number); // Haetaan varastosta ja kasvatetaan hakuja gets++; try { // Koetetaan nukkua (järkevästi) satunnainen aika sleep((int)(math.random() * 1000)); // Saadaan keskeytys, voidaan jatkaa catch (InterruptedException e) { while (storage.getfinishtime() > System.currentTimeMillis() && storage.getnumproducers() > 0); storage.retireconsumers(); // Jäädään eläkkeelle. System.out.format("Consumer #%d retiring. Made %d get operations...\n", number, gets); 7

1.5.3 Storage * Storage on Producer/Consumer dilemman ydinluokka, joka tarjoaa aksessoreita * tuottajille ja kuluttajille. public class Storage { private int contents; // Tuotettu elementti private boolean available = false; // Vapaa/varattu kytkin long finishtime = 0; // Suoritusaika (lopetusaika) int producers = 0, consumers = 0; // Olemassa olevat Producerit ja // Consumerit * Konstruktori, asettaa ajoajan * @param secondstorun public Storage(long secondstorun) { this.finishtime = secondstorun*1000 + System.currentTimeMillis(); * Lisää tuottajan public synchronized void addproducers() { producers++; * Vähentää tuottajan ja herättää nukkuvat kuluttajat (ja tuottajat) public synchronized void retireproducers() { producers--; if(producers == 0) // Mikäli ei ole enää aktiivisia tuottajia, // kerrotaan siitä: notifyall(); // Herätys kuluttajille deadlockin välttämiseksi * Palauttaa tuottajien määrän public synchronized int getnumproducers() { return producers; * Lisää kuluttajan public synchronized void addconsumers() { 8

consumers++; * Vähentää kuluttajan ja herättää tuottajat (ja kuluttajat) public synchronized void retireconsumers() { consumers--; if(consumers == 0) // Mikäli ei ole enää aktiivisia kuluttajia, // kerrotaan siitä: notifyall(); // Herätys tuottajille deadlockin välttämiseksi * Palauttaa kuluttajien määrän public synchronized int getnumconsumers() { return consumers; * Palauttaa lopetusajan public long getfinishtime() { return this.finishtime; * Hakumetodi, palauttaa tuottajan tuottaman elementin tai lopettaa. * @param who public synchronized int get(int who) { while (available == false) { try { wait(); // Odotellaan, kunnes huomautetaan, että kulutettavaa // on tarjolla catch (InterruptedException e) { // Saatiin keskeytys, voidaan jatkaa. if(producers == 0) { return 0; // Mikäli ei ole enää tuottajia, ilmoitetaan siitä available = false; System.out.format("Consumer %d got: %d%n", who, contents); notifyall(); // Herätetään nukkujat, saadaan "kriittinen vaihe" // valmiiksi. return contents; 9

* Tuottometodi: Kuka tuottaa ja minkä elementin * @param who * @param value public synchronized void put(int who, int value) { while (available == true) { try { wait(); // Odotellaan, että saadaan tuottovuoro catch (InterruptedException e) { // Saatiin keskeytys, voidaan jatkaa if(consumers == 0) return; // Mikäli ei ole kuluttajille joille tuottaa, on turha // tuottaa. // Muutoin tuotetaan elementti, ja asetetaan saatavilla // -lippu contents = value; available = true; System.out.format("Producer %d put: %d%n", who, contents); notifyall(); // Kerrotaan edistyksestä odottaville prosesseille 10

1.5.4 Producer-Consumer test import java.util.vector; public class ProducerConsumerTest { public static void main(string[] args) { int n, m; long secondtorunapp = 10; // Kauanko ajetaan? n = Integer.parseInt(args[0]); // Oletetaan parametreinä tuottajat m = Integer.parseInt(args[1]); // ja kuluttajat Vector<Producer> producers = new Vector<Producer>(); // Luodaan säilöt Vector<Consumer> consumers = new Vector<Consumer>(); Storage storage = new Storage(secondToRunApp); // Ja tuotto/kulutus // -ympäristö System.out.format("Creating:\n %d consumers\n %d producers\n\n",m, n); for(int i = 0; i < n; i++) // Lisätään tuottajat producers.add(new Producer(storage, i+1)); for(int i = 0; i < m; i++) //... ja kuluttajat consumers.add(new Consumer(storage, i+1)); System.out.println("\nStarting threads...\n"); for(producer p : producers) // Käynnistetään säikeet // (Huom. Java:n foreach) p.start(); for(consumer c : consumers) c.start(); 11