Java-kielellä säiekoodaus on suhteellisen selkeää, kunhan
asiaan malttaa perehtyä.
Säie - omaa sukua Thread
Javassa on kaksi tapaa kirjoittaa säieominaisuuksilla
varustettu luokka. Suoraviivaisinta on periyttää uusi luokka
kantaluokasta Thread, ja ylittää sen run-metodi
perillisessä. Toinen konsti on laajentaa jo olemassa olevaa
luokkaa Runnable-liittymällä, jonka myötä
säieominaisuudet siirtyvät mihin tahansa luokkaan.
Säieolio käynnistetään metodilla start, joka
suorittaa run-metodin. Run:ia ei koskaan kutsuta suoraan. Syy
tähän on yksinkertainen: vain käyttöjärjestelmä voi luoda
säikeitä. Start on tilaus, jonka vastineeksi käyttöjärjestelmä
toimittaa uuden säikeen tilaajalle. Run-metodin kutsuminen
suoraan toteuttaa säieluokkaan kirjoitetun koodin, mutta
käyttäen samaa säiettä kuin kutsuja - eli normaaliin
peräkkäiseen tyyliin rinnakkaisen sijaan.
Kun säie on suorittanut run-metodiinsa kirjoitetut käskyt,
sen elämä päättyy. Säieolion starttia voi kutsua vain kerran
olion elinaikana. Uutta käynnistystä varten oliosta on luotava
uusi ilmentymä. Oheisessa taulukossa on malli säieolion
luomisesta:
Java2-komento |
Selitys |
Philosopher Descartes; |
Määritellään Thread-luokasta periytetyn
Philosopher-luokan edustaja Descartes |
Descartes = new Philosopher(this); |
Luodaan Descartes-olioksi Philosopher-ilmentymä.
Philosopher-luokassa on määritetty kuuntelijaolio, joka
välitetään luokan konstruktorille. Tässä kuuntelijaksi
esitetään säikeen luovaa oliota itseään (this). |
Descartes.setName("Descartes"); |
Annetaan oliolle nimi - Thread-luokassa on valmiina
metodi setName() |
Descartes.setDaemon(true); |
Descartes-oliosta tehdään demoni. Se ei elä
luojaansa kauemmin. |
Descartes.setPriority(4); |
Descartesin kiireellisyysasteeksi laitetaan arvo 4.
Prioriteetti on lukuarvo väliltä 1 -10. Mitä suurempi
luku sitä enemmän säikeelle myönnetään
suoritusaikaa. |
Descartes.start(); |
Käynnistetään Descartes, jonka jälkeen se toimii
omia aikojaan käynnistäneen luojasäikeen
rinnalla. |
Synchronized
Olio lukitaan Javassa komennolla synchronized(olio). Myös
pelkän metodin voi synkronoida tyyliin: public
synchronized void teeJotain()
Ero lukitustapojen välillä on puhtaasti semanttinen.
Synkronointi lukitsee aina koko olion - eli jos olio A kutsuu
olion B synkronoitua metodia, räpsähtää koko B-olio säppiin.
Lukituksen aikana vain A pääsee käsiksi B:n metodeihin.
Lukitsija voi kohdistaa lukittavaan useita peräkkäisiä
synkronointikäskyjä. Lukko puree vain kilpaileviin säikeisiin
- eli "itseensä lukkiutumista" ei tarvitse pelätä.
Mikäli jokin muu olio, esimerkiksi C, on ehtinyt lukita B:n
ensin, joutuu A odottamaan, kunnes C vapauttaa lukon. Lukko
kirpoaa, kun lukitsijan suoritus ennättää
synchronized-komennon jälkeiseen sulkevaan aaltosulkuun:
/* Olio A */
Synchronized(B)
{ /* B on lukittu, muut säikeet eivät pääse kiinni B:hen ennen
kuin lukko on taas auki */
B.teeSita();
B.teeTata();
} /* tämä aaltosulku vapauttaa lukon */
|
Yield
Joissain käyttöjärjestelmissä säikeet onnistuvat rohmuamaan
koko prosessoriajan kerran käynnistyttyään. Mikäli säikeen
run-metodissa on hellittämättömiä ikisilmukoita, kannattaa
iteroinnin keskelle istuttaa yield-komento. Se antaa muillekin
säikeille hetkeksi mahdollisuuden päästä mukaan suoritukseen.
Yield ei pysäytä komennon esittävää säiettä, toisin kuin
sleep-käsky. Käytä yield:iä, niin ohjelmasi toimii kiltisti
kaikissa käyttöjärjestelmissä.
Sleep
Säikeen laittaminen nokosille vapauttaa prosessoriaikaa
muille säikeille. Unikomennossa määritetään millisekunteina
aika, jonka säie viettää höyhensaarilla. Sleep(1000) pistää
säikeen sekunniksi ettosille.
Wait ja notify
Säieorkesterin johtamiseen sovelias apuri on komento wait,
joka pysäyttää säikeen määräämättömäksi ajaksi, kunnes säie
kannustetaan jatkamaan suoritustaan notify-komennolla.
NotifyAll hätistelee kaikki pysäytetyt säikeet liikkeelle.
Interrupt
Joskus säie on tarpeen panna päiviltä kertaheitolla.
Komento interrupt nostaa säikeessä lipun, johon reagointi on
jätetty ohjelmankirjoittajan vastuulle. Samoin kuin aiemmin
suosittelimme yield-komennon sijoittelua säielogiikan
tiukkoihin silmukoihin, on sitäkin tärkeämpää tarkkailla
keskeytyslipun tilaa. Jos ruutulippu liehuu, lopetellaan
run-metodi hallitusti. Interrupt aiheuttaa nukkuvassa
säikeessä virheen heittävän keskeytyksen, ja siksi
sleep-komento onkin ympäröitävä try-catch-rakenteella.
Malliohjelma
Philosophers.java
Niille, joita kiinnostaa teorian soveltaminen käytännössä,
olemme laatineet java2-kielisen malliohjelman.
Kopioi
Java-kielinen lähdekoodi kotiin:
Filosofi.zip
(187 kt)
Sovelluksen kokoonpano:
Java-luokka |
Toiminta |
Philosophers.java |
Käyttöliittymäikkuna. Operatiivinen keskus, joka
piirtää sovellusikkunan ja kanavoi käyttäjän
toimenpiteet jäsenolioilleen. |
ButtonPanel.java |
Painikevalikko. Ikkunan vasemman laidan painikkeet.
"Aloita" ja Lopeta". Painikkeilla käynnistetään ja
pysäytetään viiden filosofisäikeen illanvietto
ravintolassa. |
RestaurantPanel.java |
Ravintolapaneeli. Käyttöliittymässä näkyvä
pelkistetty ravintolasali pyöreän pöydän ympärillä
istuvine filosofeineen vastaa säikeiden monitoroinnista.
Tämä luokka myös perustaa ja tappaa säikeet. Lisäksi se
välittää sekä painikevalikon että säätöpaneelin käskyt
säikeille. |
SettingPanel.java |
Pääikkunan alareunan liukuvalitsimet ruokahalulle,
nälkiintymiselle ja ruokailun kestoajalle. |
Philosopher.java |
Säieluokka. Jokainen viidestä filosofista on tämän
luokan ilmentymä. |
PopupPri.java |
Ponnahdusvalikko filosofisäikeiden
priorisointiin. |
PhListener.java |
Säietapahtumien kuuntelijaliittymä, jonka
RestaurantPanel toteuttaa. |
PopListener.java |
Ponnahdusvalikon kuuntelijaliittymä, jonka
RestaurantPanel toteuttaa. |
SbarListener.java |
Liukuvalintojen kuuntelijaliittymä, jonka
käyttöliittymä (Philosophers) toteuttaa. |
WinExit.java |
Pääsäikeen suorituksen ikkunan sulkemisen yhteydessä
päättävä luokka. |
CommonFunctions.java |
Yleiskäyttöisiä funktioita, kuten merkkijonon
kokonaislukukonversio yms. |
Constants.java |
Globaalit vakiomuuttujat, joita eri luokat
tarvitsevat. |
Säikeiden kannalta kiinnostavimmat ohjelman osat ovat
RestaurantPanel.java ja Philosopher.java.
Muut luokat keskittyvät käyttöliittymän yleiseen
muodostamiseen.
Lähdekoodit ovat .java-loppuisia, käännetyt objektit
puolestaan .class-päätteisiä. Lähdekoodit on kirjoitettu
Modelworks:in JPadPro:lla, josta voi noutaa 30-päivän ilmaisen
kokeiluversion osoitteesta http://www.modelworks.com/. Lähdekoodeja voi
toki muokata muillakin välineillä, kuten WordPad:lla, MS
Wordilla tai millä tahansa muulla teksturilla.
Ohjelman suorittamiseksi täytyy koneelta löytyä Sun:in
Java2 JRE (Java Runtime Environment). Sen voi
kopioida ilmaiseksi Sun Microsystemsin kotisivulta http://www.sun.com/. Asenna oheisen
zip-tiedoston sisältö esimerkiksi kansioon nimeltä
c:\filosofit.
Zipin purettuasi löydät java-luokkien ja kuvatiedostojen
seasta philosophers.bat-komentojonon. Muuta
komentojonon polkumääreet oman koneesi java-hakemistoa ja
filosofi-kansiota vastaaviksi. Bat-tiedostossa on ohjeet
kuinka toimia. Kun koneellasi on Javan-ajoympäristö,
Philosophers-sovellus ja ajantasainen ajojono, voit käynnistää
sovelluksen tuplaklikkaamalla Philosophers.bat-tiedoston
kuvaketta.
Säikeiden tilat
Alkutilassa pöydän ympärillä näkyy viisi filosofia ja viisi
syömäpuikkoa. Ajattelevat filosofit näyttävät tältä:
Kun filosofia alkaa nälättää, hän nappaa oikealta
puoleltaan puikon, jonka seurauksena pöydältä häviää vapaana
oleva puikko ja filosofin kuvaan lisätään puikkoa puristava
koura:
Kun filosofi onnistuu nappaamaan toisenkin puikon, hän
siirtyy syömistilaan, joka ilmaistaan avonaisella
suulla:
Mikäli nälänsietokyky ei ole maksimiarvossaan
(äärioikealla) voi filosofi nääntyä nälkään. Kyseisen tilan
osoittaa filosofin muuttuminen pääkalloksi:
Kun kaikki filosofit onnistuvat nappaamaan vain yhden
puikon, seurauksena on lukkiutuma, jota kuvataan syömäpuikon
lävistämällä pääkallolla: