Ohjelmointi

Java 101: Java-ketjujen ymmärtäminen, osa 1: Esittely ketjuista ja ajettavista

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 Lankaon 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 Lankaon 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 Lankavirheenkorjauksen apuvälineet ja käyttäjäkierteet verrattuna daemon-säikeisiin.

Esitän loput Lankamenetelmiä 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 luo Lanka objekti oletusnimellä
  • Lanka (merkkijono), joka luo Lanka objektin nimellä, jonka nimi 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ää StackOverflowErrors. Liian suuri koko voi kuitenkin johtaa OutOfMemoryErrors. 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 Lankatai 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 Lankatai 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 kohtaan main ()
  • 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 Lankastaattinen 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 CalcPI1lä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
$config[zx-auto] not found$config[zx-overlay] not found