Tämä artikkeli on ensimmäinen neljässä osassa Java 101 sarja, joka tutkii Java-ketjuja. Vaikka luulet, että Java-ketjuttaminen olisi haastavaa ymmärtää, aion näyttää sinulle, että ketjut ovat helposti ymmärrettäviä. Tässä artikkelissa esitän sinulle Java-ketjut ja juoksevat. Seuraavissa artikkeleissa tutkimme synkronointia (lukkojen kautta), synkronointiongelmia (kuten umpikujaan), odotus / ilmoitus -mekanismia, ajoitusta (prioriteetilla ja ilman), ketjun keskeytystä, ajastimia, volatiliteettia, ketjujen ryhmiä ja säikeiden paikallisia muuttujia .
Huomaa, että tämä artikkeli (osa JavaWorld-arkistoa) päivitettiin uusilla koodiluetteloilla ja ladattavalla lähdekoodilla toukokuussa 2013.
Ymmärtäminen Java-ketjuista - lue koko sarja
- Osa 1: Esittely kierteet ja ajettavat
- Osa 2: Synkronointi
- Osa 3: Langan ajoitus ja odotus / ilmoitus
- Osa 4: Lankaryhmät ja volatiliteetti
Mikä on ketju?
Käsitteellisesti a lanka ei ole vaikea ymmärtää: se on itsenäinen toteutuspolku ohjelmakoodin kautta. Kun useita ketjuja suoritetaan, yhden ketjun polku saman koodin läpi eroaa yleensä muista. Oletetaan esimerkiksi, että yksi ketju suorittaa if-else-käskyn tavukoodiekvivalentin jos
osa, kun taas toinen ketju suorittaa tavun koodivastaavan muu
osa. Kuinka JVM seuraa kunkin ketjun suoritusta? JVM antaa jokaiselle säikeelle oman metodikutsupinonsa. Nykyisen tavukoodikäskyn seurannan lisäksi method-call -pino seuraa paikallisia muuttujia, parametreja, jotka JVM siirtää menetelmälle, ja menetelmän palautusarvoa.
Kun useat ketjut suorittavat tavukoodikäskyjaksoja samassa ohjelmassa, kyseinen toiminto tunnetaan nimellä monisäikeinen. Monisäikeisyys hyödyntää ohjelmaa monin tavoin:
- Monisäikeiset GUI (graafinen käyttöliittymä) -pohjaiset ohjelmat pysyvät reagoivina käyttäjiin, kun he suorittavat muita tehtäviä, kuten asiakirjojen uudelleenmuokkaamista tai asiakirjan tulostamista.
- Kierteelliset ohjelmat suoritetaan tyypillisesti nopeammin kuin langattomat vastaavat. Tämä pätee erityisesti moniprosessorikoneessa käynnissä oleviin ketjuihin, joissa jokaisella ketjulla on oma prosessori.
Java suorittaa monisäikeisyyden sen kautta java.lang.Thread
luokassa. Jokainen Lanka
object kuvaa yhden suorituslangan. Suoritus tapahtuu vuonna Lanka
on juosta()
menetelmä. Koska oletuksena juosta()
menetelmä ei tee mitään, sinun on aliluokka Lanka
ja ohittaa juosta()
tehdä hyödyllistä työtä. Mausta langoista ja monisäikeisyydestä Lanka
, tutki luetteloa 1:
Listaus 1. ThreadDemo.java
// ThreadDemo.java-luokka ThreadDemo {public static void main (String [] args) {MyThread mt = new MyThread (); mt.start (); for (int i = 0; i <50; i ++) System.out.println ("i =" + i + ", i * i =" + i * i); }} luokka MyThread laajentaa ketjua {public void run () {for (int count = 1, row = 1; row <20; row ++, count ++) {for (int i = 0; i <count; i ++) System.out. Tulosta ('*'); System.out.print ('\ n'); }}}
Listaus 1 esittää lähdekoodin luokista koostuvalle sovellukselle ThreadDemo
ja MyThread
. Luokka ThreadDemo
ajaa sovellusta luomalla a MyThread
objektin, aloittamalla säie, joka liittyy kyseiseen objektiin, ja suorittamalla jonkin koodin neliötaulukon tulostamiseksi. Verrattuna, MyThread
ohittaa Lanka
on juosta()
tapa tulostaa (vakiolähtövirtaan) tähtikirjaimista koostuva suorakulmainen kolmio.
Viestien ajoitus ja JVM
Suurin osa (ellei kaikki) JVM-toteutuksista käyttää taustalla olevan alustan ketjutusominaisuuksia. Koska nämä ominaisuudet ovat alustakohtaisia, monisäikeisten ohjelmien tulostusjärjestys voi poiketa jonkun muun tuotoksen järjestyksestä. Tämä ero johtuu aikataulusta, aiheesta, jota tutkin myöhemmin tässä sarjassa.
Kun kirjoitat java ThreadDemo
sovelluksen suorittamiseksi JVM luo aloituslangan suorituksen, joka suorittaa main ()
menetelmä. Toteuttamalla mt.start ();
, aloituslanka käskee JVM: n luomaan toisen suorituslangan, joka suorittaa tavun koodikäskyt, jotka käsittävät MyThread
esine juosta()
menetelmä. Kun alkaa()
method palaa, aloituslanka suorittaa sen varten
-silmukka tulostaaksesi neliötaulukon, kun uusi säie suorittaa juosta()
tapa tulostaa suorakulmainen kolmio.
Miltä tuotos näyttää? Juosta ThreadDemo
saada selville. Huomaat, että jokaisen ketjun ulostulo pyrkii leikkaamaan toisen lähdön kanssa. Tämä johtuu siitä, että molemmat ketjut lähettävät lähdön samaan vakiolähtöön.
Lanka-luokka
Jotta oppisit taitavasti kirjoittamaan monisäikeistä koodia, sinun on ensin ymmärrettävä eri menetelmät, jotka muodostavat Lanka
luokassa. Tässä osassa tutkitaan monia näistä menetelmistä. Erityisesti opit ketjujen aloittamismenetelmistä, ketjujen nimeämisestä, ketjujen nukkumisesta, langan olemassaolon määrittämisestä, yhden ketjun liittämisestä toiseen ketjuun ja kaikkien aktiivisten säikeiden luetteloimisesta nykyisen ketjun ketjuryhmässä ja alaryhmissä. Keskustelen myös Lanka
virheenkorjauksen apuvälineet ja käyttäjäkierteet verrattuna daemon-säikeisiin.
Esitän loput Lanka
menetelmiä seuraavissa artikkeleissa, lukuun ottamatta Sunin vanhentuneita menetelmiä.
Vanhentuneet menetelmät
Sun on vanhentanut useita Lanka
menetelmiä, kuten keskeyttää()
ja jatkaa()
, koska ne voivat lukita ohjelmat tai vahingoittaa esineitä. Tämän seurauksena sinun ei pitäisi soittaa heille koodissasi. Katso SDK-dokumentaatiosta näiden menetelmien kiertäminen. En kata vanhentuneita menetelmiä tässä sarjassa.
Rakentavat langat
Lanka
on kahdeksan rakentajaa. Yksinkertaisimmat ovat:
Lanka()
, joka luoLanka
objekti oletusnimelläLanka (merkkijono)
, joka luoLanka
objektin nimellä, jonkanimi
argumentti määrittelee
Seuraavat yksinkertaisimmat rakentajat ovat Lanka (ajettava kohde)
ja Lanka (ajettava kohde, merkkijonon nimi)
. Lukuun ottamatta Ajettava
parametrit, nämä konstruktorit ovat identtisiä edellä mainittujen rakentajien kanssa. Ero: Ajettava
parametrit tunnistavat ulkopuoliset kohteet Lanka
jotka tarjoavat juosta()
menetelmiä. (Opit Ajettava
myöhemmin tässä artikkelissa.) Viimeiset neljä rakentajaa muistuttavat Lanka (merkkijono)
, Lanka (ajettava kohde)
ja Lanka (ajettava kohde, merkkijonon nimi)
; lopullisiin rakentajiin kuuluu kuitenkin myös a ThreadGroup
argumentti organisatorisiin tarkoituksiin.
Yksi viimeisistä neljästä rakentajasta, Lanka (ThreadGroup-ryhmä, Runnable-kohde, String-nimi, pitkä stackSize)
, on mielenkiintoinen siinä mielessä, että sen avulla voit määrittää langan method-kutsupinon halutun koon. Koon määrittäminen osoittautuu hyödylliseksi ohjelmissa, joissa käytetään menetelmiä, jotka käyttävät rekursiota - suoritustekniikkaa, jossa menetelmä kutsuu itseään toistuvasti - ratkaisemaan tyylikkäästi tietyt ongelmat. Asettamalla pinon koon nimenomaisesti, voit joskus estää StackOverflowError
s. Liian suuri koko voi kuitenkin johtaa OutOfMemoryError
s. Lisäksi Sun pitää menetelmäpuhelupinon kokoa alustasta riippuvana. Alustasta riippuen method-call -pinon koko saattaa muuttua. Ajattele siksi huolellisesti ohjelmasi seurauksia ennen kuin kirjoitat kutsuvan koodin Lanka (ThreadGroup-ryhmä, Runnable-kohde, String-nimi, pitkä stackSize)
.
Käynnistä ajoneuvosi
Kierteet muistuttavat ajoneuvoja: ne siirtävät ohjelmia alusta loppuun. Lanka
ja Lanka
alaluokan objektit eivät ole ketjuja. Sen sijaan ne kuvaavat ketjun määritteet, kuten sen nimen, ja sisältävät koodin (a: n kautta juosta()
menetelmä), jonka lanka suorittaa. Kun on aika uudelle langalle suorittaa juosta()
, toinen säie kutsuu Lanka
tai sen aliluokan objektin alkaa()
menetelmä. Esimerkiksi toisen ketjun aloittamiseksi sovelluksen aloitussäike - joka suoritetaan main ()
—Puhelut alkaa()
. Vastauksena JVM: n langankäsittelykoodi toimii alustan kanssa varmistaakseen, että ketju alustetaan oikein ja kutsuu a Lanka
tai sen aliluokan objektin juosta()
menetelmä.
Kerran alkaa()
valmis, useita ketjuja suoritetaan. Koska meillä on tapana ajatella lineaarisesti, meidän on usein vaikea ymmärtää samanaikainen (samanaikainen) toiminta, joka tapahtuu, kun kaksi tai useampi ketju on käynnissä. Siksi sinun tulisi tutkia kaavio, joka osoittaa, missä säike suorittaa (sen sijainti) ajan suhteen. Alla olevassa kuvassa on tällainen kaavio.

Kaavio näyttää useita merkittäviä ajanjaksoja:
- Aloituslangan alustus
- Hetki, jonka säie alkaa suorittaa
main ()
- Hetki, jonka säie alkaa suorittaa
alkaa()
- Hetki
alkaa()
luo uuden ketjun ja palaa kohtaanmain ()
- Uuden langan alustus
- Heti kun uusi ketju alkaa suorittaa
juosta()
- Kunkin langan eri hetket päättyvät
Huomaa, että uuden ketjun alustus, sen suorittaminen juosta()
, ja sen päättyminen tapahtuu samanaikaisesti aloituslangan suorituksen kanssa. Huomaa myös, että viestiketjun jälkeen alkaa()
, myöhemmät kutsut tähän menetelmään ennen juosta()
menetelmä poistuu syystä alkaa()
heittää a java.lang.IllegalThreadStateException
esine.
Mikä on nimessä?
Virheenkorjausistunnon aikana yhden säikeen erottaminen toisesta käyttäjäystävällisellä tavalla osoittautuu hyödylliseksi. Jotta ketjut voidaan erottaa toisistaan, Java yhdistää nimen ketjuun. Tämä nimi on oletuksena Lanka
, yhdysmerkki ja nollaan perustuva kokonaisluku. Voit hyväksyä Java-oletussäikeiden nimet tai valita oman. Mukautettujen nimien mukauttamiseksi Lanka
tarjoaa rakentajia, jotka ottavat nimi
argumentit ja a setName (merkkijono)
menetelmä. Lanka
tarjoaa myös getName ()
menetelmä, joka palauttaa nykyisen nimen. Listaus 2 osoittaa, kuinka mukautettu nimi voidaan luoda Lanka (merkkijono)
konstruktori ja noudata nykyistä nimeä juosta()
menetelmä soittamalla getName ()
:
Listaus 2. NameThatThread.java
// NameThatThread.java class NameThatThread {public static void main (String [] args) {MyThread mt; if (args.pituus == 0) mt = uusi MyThread (); else mt = uusi MyThread (args [0]); mt.start (); }} luokka MyThread laajentaa ketjua {MyThread () {// Kääntäjä luo tavun koodiekvivalentin super (): lle; } MyThread (merkkijono) {super (nimi); // Anna nimi ketjun superluokalle} public void run () {System.out.println ("Nimeni on:" + getName ()); }}
Voit välittää valinnaisen nimi-argumentin osoitteelle MyThread
komentorivillä. Esimerkiksi, java NameThatThread X
perustaa X
ketjun nimenä. Jos et määritä nimeä, näet seuraavan tuloksen:
Nimeni on: Lanka-1
Halutessasi voit muuttaa super (nimi);
soita sisään MyThread (merkkijono)
rakentaja kutsuun setName (merkkijono)
- kuten setName (nimi);
. Viimeksi mainitulla menetelmäkutsulla saavutetaan sama tavoite - ketjun nimen määrittäminen - kuin super (nimi);
. Jätän sen sinulle harjoitukseksi.
Nimeäminen pää
Java antaa nimen tärkein
ketjuun, joka suorittaa main ()
menetelmä, aloituslanka. Nimi näkyy yleensä Poikkeus säikeessä "main"
viesti, jonka JVM: n oletuspoikkeuskäsittelijä tulostaa, kun aloituslanka heittää poikkeusobjektin.
Nukkua tai ei nukkua
Myöhemmin tässä sarakkeessa esitän sinut animaatio- piirtämällä toistuvasti yhdelle pinnalle kuvia, jotka eroavat toisistaan hieman liikkeen illuusion aikaansaamiseksi. Animaation suorittamiseksi ketjun on keskeytettävä kahden peräkkäisen kuvan näyttäminen. Kutsumus Lanka
staattinen nukkua (pitkät millit)
method pakottaa langan pysähtymään millis
millisekuntia. Toinen lanka voi mahdollisesti keskeyttää unilangan. Jos näin tapahtuu, unilanka herää ja heittää Keskeytetty poikkeus
objekti nukkua (pitkät millit)
menetelmä. Tämän seurauksena koodi, joka soittaa nukkua (pitkät millit)
täytyy ilmestyä a yrittää
esto - tai koodin menetelmän on sisällettävä Keskeytetty poikkeus
sen heittää
lauseke.
Näyttää nukkua (pitkät millit)
, Olen kirjoittanut CalcPI1
sovellus. Tuo sovellus aloittaa uuden ketjun, joka käyttää matemaattista algoritmia laskemaan matemaattisen vakion pi arvon. Kun uusi säie laskee, alkulanka keskeytyy 10 millisekunniksi soittamalla nukkua (pitkät millit)
. Kun aloituslanka on herännyt, se tulostaa pi-arvon, jonka uusi säie tallentaa muuttujaan pi
. Luettelossa 3 lahjaa CalcPI1
lähdekoodi:
Listaus 3. CalcPI1.java
// CalcPI1.java-luokka CalcPI1 {public static void main (String [] args) {MyThread mt = new MyThread (); mt.start (); kokeile {Thread.sleep (10); // Lepotila 10 millisekuntia} saalis (InterruptedException e) {} System.out.println ("pi =" + mt.pi); }} luokka MyThread laajentaa säiettä {looginen negatiivinen = tosi; kaksinkertainen pi; // Alustetaan arvoon 0.0, oletusarvoisesti public void run () {for (int i = 3; i <100000; i + = 2) {if (negatiivinen) pi - = (1.0 / i); muuten pi + = (1,0 / i); negatiivinen =! negatiivinen; } pi + = 1,0; pi * = 4,0; System.out.println ("PI-laskenta on valmis"); }}
Jos suoritat tämän ohjelman, näet samanlaisen (mutta todennäköisesti ei identtisen) lähdön kuin seuraava:
pi = -0,2146197014017295 PI-laskenta on valmis