Luento 6 T-106.1240 Ohjelmoinnin jatkokurssi T1 & T-106.1243 Ohjelmoinnin jatkokurssi L1 Luennoitsija: Otto Seppälä Kurssin WWW: http://www.cs.hut.fi/opinnot/t-106.1240/s2007
Oma Grafiikka Swing-käyttöliittymässä
Grafiikkaa Swingillä Oman grafiikan piirtäminen Swing-käyttöliittymässä on varsin helppoa. Periytä uusi luokka JPanel-luokasta Korvaa paintcomponent(graphics g)-metodi Graphics-luokan API-dokumentaatiosta selviää mitä kaikkea sillä voi tehdä piirtää ympyröitä, suorakulmioita, viivoja, tekstiä jne... Swing komponenttien paintcomponent-metodin parametri on yleensä Graphics2D-tyyppinen tyyppipakotuksen jälkeen voit piirtää myös bezier-käyriä, jne... Seuraavassa yksinkertainen esimerkki...
import java.awt.color; import java.awt.dimension; import java.awt.graphics; import javax.swing.jpanel; public class Ympyra extends JPanel { // piirretään Graphics-olion kautta ruudulle // punainen ympyrä ja tekstiä public void paintcomponent ( Graphics g ) { g.setcolor( Color.RED ); g.drawstring( "Maailman hienoin ympyrä!", 10, 10 ); g.drawoval(20,30,100,100); // pyydetään vähän enemmän tilaa ettei pack // paina komponenttia kasaan. public Dimension getpreferredsize() { return new Dimension(200,200);
import javax.swing.jframe; public class PaaIkkuna extends JFrame { public static void main( String[] args ) { PaaIkkuna ikkuna = new PaaIkkuna(); ikkuna.setvisible(true); ikkuna.pack(); public PaaIkkuna() { super("maailman hienoin ikkuna"); //Ikkunan sulkeminen lopettaa koko ohjelman this.setdefaultcloseoperation(jframe.exit_on_close); Ympyra ympyra = new Ympyra(); add(ympyra);
Säikeet ja Rinnakkaisohjelmointi
Rinnakkaisohjelmointi Sisältö Mikä on säie? Javan luokka Thread Luokan ominaisuudet Säikeen luonti ja käynnistäminen Perimällä luokka Thread Täyttämällä rajapinta Runnable Säikeiden synkronointi Monitori synchronized-määre ja synkronoidut lohkot wait() - notify() / notifyall() Muuta volatile-määre
Mikä on Säie? Prosessi Kaikissa nykyaikaisissa käyttöjärjestelmissä on mahdollista suorittaa useita ohjelmia yhtä aikaa Käyttöjärjestelmä ajaa jokaista ohjelmaa omassa prosessissaan, jakaen prosessoriaikaa ohjelmille niin, että ohjelmat näyttävät toimivan samanaikaisesti Säie Yhden prosessin sisällä myös yksittäinen ohjelma voi suorittaa eri toimintoja samanaikaisesti käyttämällä säikeitä (thread) Säikeet ovat prosesseihin verrattuna selkeästi kevyempiä
Mikä on säie? Sopivasti käytettynä säikeillä saadaan paljon etuja ohjelma voi käyttää prosessoriaikaa mahdollisimman tehokkaasti hyväkseen ohjelma voi vastata käyttäjän toimiin nopeammin jne. Jokaisella säikeellä on oma suorituspino paikallisia muuttujia (suorituspinossa) ei jaeta kaikki muu on yhteistä (keossa) Java tukee rinnakkaisohjelmointia jo kielen tasolla Selkiyttää asioita Monessa ohjelmointikielessä tuki säikeille toteutetaan jonkin ohjelmointikirjaston avulla
Luokka Thread Luokka Thread mahdollistaa säikeiden ohjaamisen sleep() pysäyttää säikeen halutuksi ajaksi interrupt() keskeyttää odotuksen start() käynnistää säikeen isalive() kertoo onko säie elossa join() odottaa säikeen kuolemista yield() antaa suoritusvuoron muille getname() kertoo säikeen nimen getpriority() kertoo säikeen prioriteetin setpriority() asettaa säikeen prioriteetin currentthread() antaa ajossaolevan säikeen
Luokka Thread Luokassa on myös vanhentuneita metodeja joiden käyttöä tulisi välttää. suspend() resume() stop()
Sleep Allaoleva ohjelma tulostaa sanan Testi kolme kertaa noin kolmen sekunnin välein public class Esimerkki { public static void main ( String[] args) { Thread tamasaie = Thread.currentThread(); try { for (int i=0; i<3; i++) { System.out.println( Testi ); tamasaie.sleep(3000); catch (InterruptedException e) {
Sleep Sleepin parametri kertoo kuinka kauan säie on pysähdyksissä (millisekunneissa) HUOM! Tämä on minimiaika. Säikeen paluu ajoon voi kestää pidempään. (Älä rakenna kelloa sleep-metodilla) Mitä muuta esimerkissä tapahtui? Viittaus ajettavan säikeen Thread-olioon haettiin Thread luokan luokkametodilla currentthread() Sleep-metodin kutsu oli sijoitettu try-catch rakenteen sisään, koska säikeen nukkuminen voidaan keskeyttää kutsumalla sen interruptmetodia
Säikeen luonti ja käynnistys Kuinka säie luodaan? Uusi säie voidaan luodaan luomalla uusi Thread-olio ja kutsumalla sen start-metodia Start käynnistää säikeen halutusta aloituskohdasta Ennen start()-metodin kutsua säie on pysähdyksissä Tämä aloituspiste voidaan antaa kahdella tavalla toteuttamalla rajapinta Runnable periyttämällä uusi luokka luokasta Thread Lopputulos on molemmissa tapauksissa sama suoritus alkaa jommankumman metodista run() run()-metodia ei kutsuta suoraan vaan Thread tekee sen kun sitä vastaava säie käynnistetään start:illa säikeen suoritus päättyy kun run()-metodista poistutaan
Rajapinta Runnable Runnable-rajapinta määrittää edellämainitun metodin public abstract void run() Runnable-olio annetaan Thread-luokan konstruktorille parametrina Thread saie = new Thread( runnableolio); Alussa säie on pysähdyksissä, mutta kun sen start()- metodia kutsustaan, säie käynnistyy ja aloittaa suorituksensa Runnable-olion run()-metodista saie.start( );
Runnable rajapinta public class Tulostaja implements Runnable { private String viesti; public Tulostaja( String tulostatama ) { this.viesti = tulostatama public void run () { Thread tamasaie = Thread.currentThread(); try { for (int i=0; i<10; i++) { tamasaie.sleep(500); System.out.println( Viesti + i); catch (InterruptedException e) {
Runnable rajapinta public class Esimerkki2 { public static void main ( String[] args){ Tulostaja t = new Tulostaja( Moi : ); Tulostaja u = new Tulostaja( Hei : ); Thread saiea = new Thread(t); Thread saieb = new Thread(u); saiea.start(); saieb.start(); try { for (int i=0; i<10; i++) { thread.currentthread().sleep(1000); System.out.println( Main + i); catch (InterruptedException e) {
Thread-luokan periminen Aloituspiste voidaan antaa myös perimällä luokka Thread ja korvaamalla sen metodi run() public void run() Kun Thread-olio luodaan konstruktorilla joka ei ota viitettä Runnable-olioon, start()-metodin kutsu kutsuu Thread-olion omaa run()-metodia Käytännössä siis Thread-olio on itse tuolloin oma Runnable()- olionsa Runnable-rajapinnan täyttäminen on monesti selkeämpi vaihtoehto.
Muutama muu metodi isalive() tiedustelee, onko tutkittu säie elossa join() odottaa kunnes säie, jonka join-metodia kutsuttiin kuolee Voidaan vaikkapa odottaa, että jonkin olennaisen tehtävän suoritus saadaan loppuun Voi olla kätevä ohjelman alasajossa Huomaa, että join() ei lopeta säikeitä vaan odottaa että ne kuolevat luonnollisesti yield() Tällä-hetkellä ajossa oleva säie luovuttaa ajovuoronsa muille
Säikeiden synkronointi
Synkronointi Kun kaksi säiettä tai useampi säie haluaa käyttää jotakin yhteistä resurssia (muuttuja, tiedosto, jne) yhtäaikaisesti, täytyy pitää huolta että vain yksi säie kerrallaan käsittelee resurssia. Esim tilanne jossa kaksi säiettä avaavat saman tiedoston ja kirjoittavat siihen. Menevätkö kirjoitetut rivit sekaisin? Toimintaa, joka takaa tämän yksi kerrallaan - periaatteen kutsutaan synkronoinniksi
Monitori Javassa synkronointi toteutetaan ns. monitorin avulla Monitori on jokin alue ohjelmakoodista, joka suojataan lukkoolion avulla - Mikä tahansa Java:n olio voi toimia lukkona Vain yksi säie kerrallaan voi olla ajossa monitorin sisällä jos jokin säie on jo ajossa monitorin sisällä, muut säikeet odottavat ulkopuolella kunnes sisällä oleva säie poistuu monitorista säie voi poistua monitorista joko niin, että se poistuu koodialueelta normaalisti tai siirtymällä odottamaan kutsumalla metodia wait() Monitorin alue rajataan synchronized-määreen avulla
Synchronized Synchronized-määreellä on kaksi käyttötapaa Määre voidaan kirjoittaa metodin eteen, jolloin se olio, jonka metodia kutsutaan toimii monitorin lukkona public class Posti { public synchronized Paketti otapaketti() { // jotain koodia public synchronized void tuopaketti( Paketti paketti ){ // jotain koodia Annetussa esimerkissä Posti-olio toimii itse lukkona, siten, että vain yksi säie kerrallaan voi suorittaa jotakin luokasta löytyvää synkronoitua metodia. paketteja ei siis voi tuoda samanaikaisesti, viedä samanaikaisesti taikka tuoda ja viedä yhtä aikaa, jolloin kukaan ei mm. voi viedä samaa pakettia kahdesti
Synchronized Toinen käyttötapa synchronized määreelle on kirjoittaa synkronoitu lohko, jonka yhteydessä määritetään olio, joka toimii lukkona public class Posti { ArrayList lahtevatpaketit; public Paketti otapaketti() { // jotain koodia synchronized( lahtevatpaketit ) { // koodia joka käsittelee pakettien jonoa // jotain koodia Tässä lahtevatpaketit on lukko, joka estää että synkronoituun lohkoon pääsisi useampi säie kerrallaan
wait() ja notify() Entäs jos postissa ei olekaan paketteja? Tietenkin voitaisiin kirjoittaa silmukka joka käy hakemassapakettia kunnes saa sellaisen Käytännössä tämä veisi kaikki järjestelmän suoritustehot turhan silmukan suoritukseen Ongelma ratkeaa metodeilla wait() ja notify() Kutsumalla lukko-olion metodia wait() säie poistuu monitorista ja siirtyy odottamaan Kun joku toinen säie kutsuu lukko-olion metodia notify(), se herättää yhden wait-jonon säikeen odottamaan pääsyä takaisin suoritukseen wait-kutsua seuraavalta riviltä Kaikki wait-jonon säikeet voi siirtää ready-jonoon komennolla notifyall()
public class Posti { private ArrayList<Paketti> paketit = new ArrayList<Paketti>(); public static final int POSTIN_KOKO = 15; public synchronized Paketti otapaketti() { while( paketit.isempty() ) { wait(); Paketti poistettava = paketit.remove(0); notify(); return poistettava; public synchronized void tuopaketti( Paketti p ) { while( paketit.size() >= POSTIN_KOKO ) { wait(); paketit.add( p ); notify();
Javan monitori ajovalmiit säikeet Edellinen säie poistuu monitorista Monitori synkronoitu metodi monitorissa oleva säie siirtää yhden säikeen ajovalmiuteen kutsumalla notify() notify Monitorissa juuri nyt oleva säie synkronoitu metodi odottavat säikeet wait synkronoitu metodi Monitorissa oleva säie siirtää itsensä odottamaan kutsumalla wait()
Volatile Säikeet voivat tehokkuussyistä käyttää väliaikaisesti omia kopioita joistakin yhteisistä muuttujista Mikäli on erityisen tärkeää että jonkin muuttujan tila on yksikäsitteinen, kirjoitetaan muuttujan määrittelyyn sana volatile Tämä pakottaa javan käyttämään vain yhtä kopiota muuttujasta Samaan päästään luonnollisesti myös sopivalla synkronoinnilla Huom: volatile ei ole tapa toteuttaa synkronointia
Säikeet ja GUI Viime luennolla katsottiin kuinka graafinen käyttöliittymä toteutetaan Javalla Luodaan jokin päätason säiliö Luodaan muista säiliöitä ja nappuloita jne Valitaan säiliöille sopivat layout managerit Lisätään nappulat ja säiliöt päätason säiliöön Lisätään nappuloihin jne. tapahtumankuuntelijat Laitetaan päätason säiliö näkyviin
Säikeet ja GUI Tämä toimii hyvin kun tapahtumankuuntelijan tekemä työ on tehtävissä nopeasti. Entä jos nappulan painaminen aloittaa animaation tai raskaan laskutoimituksen? Käyttöliittymä pysähtyy siksi aikaa kun tapahtumankuuntelijan metodi suorittaa jotakin tehtävää. Näyttöäkään ei ehkä päivitetä. Tämän saa ratkaistua käyttämällä säikeitä. Tutustu luentoesimerkkiin www-sivuilla