Ohjelmointi

Java 101: Java-ketjujen ymmärtäminen, osa 3: Langan ajoitus ja odotus / ilmoitus

Tässä kuussa jatkan neliosaista johdantoa Java-ketjuihin keskittymällä ketjujen ajoitukseen, odotus- / ilmoitusmekanismiin ja ketjun keskeytymiseen. Tutki, kuinka joko JVM tai käyttöjärjestelmän ketjujen ajoittaja valitsee seuraavan ketjun suoritettavaksi. Kuten huomaat, prioriteetti on tärkeä ketjun ajoittajan valinnassa. Tutki, miten säie odottaa, kunnes se saa ilmoituksen toisesta säikeestä, ennen kuin se jatkaa suoritusta, ja opit käyttämään odotus- / ilmoitusmekanismia kahden ketjun suorittamisen koordinoimiseksi tuottaja-kuluttaja-suhteessa. Lopuksi opit herättämään joko nukkuva tai odottava lanka ennenaikaisesti langan päättämistä tai muita tehtäviä varten. Opetan myös, kuinka lanka, joka ei nuku eikä odota, havaitsee keskeytyspyynnön toisesta säikeestä.

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, odotus / ilmoitus ja langan keskeytys
  • Osa 4: Lankaryhmät, volatiliteetti, säie-paikalliset muuttujat, ajastimet ja langan kuolema

Viestien aikataulutus

Idealisoidussa maailmassa kaikilla ohjelmasäikeillä olisi omat prosessorit, joilla ne voivat toimia. Siihen saakka, kun tietokoneilla on tuhansia tai miljoonia suorittimia, ketjujen on usein jaettava yksi tai useampi prosessori. JVM tai taustalla olevan alustan käyttöjärjestelmä selvittää prosessorin resurssin jakamisen ketjujen kesken - tehtävä tunnetaan nimellä ketjun ajoitus. Se osa JVM: ää tai käyttöjärjestelmää, joka suorittaa ketjun ajoituksen, on a ketjun ajastin.

merkintä: Keskustellakseni ketjun ajoituksen keskustelua keskityn ketjujen ajoitukseen yhden prosessorin yhteydessä. Voit ekstrapoloida tämän keskustelun useille prosessoreille. Jätän sen tehtävän sinulle.

Muista kaksi tärkeää aihetta ketjun ajoituksesta:

  1. Java ei pakota virtuaalikoneita ajoittamaan ketjuja tietyllä tavalla tai sisältämään ketjujen ajoitinta. Tämä tarkoittaa alustasta riippuvaa ketjun ajoitusta. Siksi sinun on oltava varovainen kirjoittaessasi Java-ohjelmaa, jonka käyttäytyminen riippuu säikeiden ajoituksesta ja sen on toimittava johdonmukaisesti eri alustoilla.
  2. Onneksi kirjoittaessasi Java-ohjelmia sinun on mietittävä, kuinka Java ajoittaa ketjut vain, kun ainakin yksi ohjelman ketjuista käyttää voimakkaasti prosessoria pitkiä aikoja ja kyseisen ketjun suorituksen välitulokset osoittautuvat tärkeiksi. Esimerkiksi sovelma sisältää langan, joka luo kuvan dynaamisesti. Ajoittain haluat, että maalauslanka piirtää kuvan nykyisen sisällön, jotta käyttäjä voi nähdä, miten kuva etenee. Harkitse ketjun ajoitus varmistaaksesi, että laskennankierre ei hallitse prosessoria.

Tutki ohjelmaa, joka luo kaksi prosessoriintensiivistä säiettä:

Listaus 1. SchedDemo.java

// SchedDemo.java-luokka SchedDemo {public static void main (String [] args) {new CalcThread ("CalcThread A"). Start (); uusi CalcThread ("CalcThread B"). start (); }} luokka CalcThread laajentaa säiettä {CalcThread (String name) {// välittää nimen säikeelle. super (nimi); } double calcPI () {looginen negatiivinen = tosi; kaksinkertainen pi = 0,0; for (int i = 3; i <100000; i + = 2) {jos (negatiivinen) pi - = (1,0 / i); muuten pi + = (1,0 / i); negatiivinen =! negatiivinen; } pi + = 1,0; pi * = 4,0; paluu pi; } public void run () {for (int i = 0; i <5; i ++) System.out.println (getName () + ":" + calcPI ()); }}

SchedDemo luo kaksi ketjua, joista kukin laskee pi-arvon (viisi kertaa) ja tulostaa jokaisen tuloksen. Riippuen siitä, miten JVM-toteutus ajoittaa ketjut, saatat nähdä seuraavanlaisen lähdön:

CalcThread V: +3,1415726535897894 CalcThread B: +3,1415726535897894 CalcThread V: +3,1415726535897894 CalcThread V: +3,1415726535897894 CalcThread B: +3,1415726535897894 CalcThread V: +3,1415726535897894 CalcThread V: +3,1415726535897894 CalcThread B: +3,1415726535897894 CalcThread B: +3,1415726535897894 CalcThread B: +3,1415726535897894

Yllä olevan lähdön mukaan ketjun ajastin jakaa prosessorin molempien säikeiden kesken. Voit kuitenkin nähdä samanlaisen tuotoksen:

CalcThread V: +3,1415726535897894 CalcThread V: +3,1415726535897894 CalcThread V: +3,1415726535897894 CalcThread V: +3,1415726535897894 CalcThread V: +3,1415726535897894 CalcThread B: +3,1415726535897894 CalcThread B: +3,1415726535897894 CalcThread B: +3,1415726535897894 CalcThread B: +3,1415726535897894 CalcThread B: +3,1415726535897894

Yllä oleva tuotos osoittaa ketjun ajoittajan suosivan yhtä ketjua toiseen. Kaksi yllä olevaa lähtöä kuvaavat kahta yleistä säikeiden ajoitinluokkaa: vihreä ja natiivi. Tutkin heidän käyttäytymiserojaan tulevissa osioissa. Keskustellessani kustakin luokasta viittaan ketjutilat, joista on neljä:

  1. Alkuperäinen tila: Ohjelma on luonut ketjun säieobjektin, mutta säiettä ei ole vielä olemassa, koska säieobjekti on alkaa() menetelmää ei ole vielä kutsuttu.
  2. Suoritettava tila: Tämä on ketjun oletustila. Soiton jälkeen alkaa() valmistuu, säie tulee ajettavaksi riippumatta siitä, onko ketju käynnissä, toisin sanoen suorittimen avulla. Vaikka monet ketjut voivat olla ajettavia, vain yksi toimii tällä hetkellä. Säikeiden ajoittajat määrittävät suoritettavan ketjun prosessorille.
  3. Estetty tila: Kun lanka suorittaa nukkua(), odota()tai liittyä seuraan() menetelmiä, kun ketju yrittää lukea tietoja, joita ei ole vielä saatavana verkosta, ja kun ketju odottaa lukituksen hankkimista, kyseinen ketju on estetyssä tilassa: se ei ole käynnissä eikä suoritusasennossa. (Voit luultavasti ajatella muita aikoja, jolloin säie odottaa jonkun tapahtumista.) Kun estetty säie vapautetaan, kyseinen säie siirtyy ajettavaan tilaan.
  4. Lopetustila: Kun toteutus jättää langan juosta() menetelmä, kyseinen säie on lopetustilassa. Toisin sanoen lanka lakkaa olemasta.

Kuinka ketjun ajastin valitsee suoritettavan säikeen? Aloitan vastauksen tähän kysymykseen keskustellessani vihreän langan ajoituksesta. Viimeistelen vastauksen keskustellessani alkuperäisen ketjun ajoituksesta.

Vihreän langan aikataulutus

Kaikki käyttöjärjestelmät, esimerkiksi muinainen Microsoft Windows 3.1 -käyttöjärjestelmä, eivät tue ketjuja. Tällaisia ​​järjestelmiä varten Sun Microsystems voi suunnitella JVM: n, joka jakaa sen ainoan suorituslangan useisiin säikeisiin. JVM (ei taustalla olevan alustan käyttöjärjestelmä) toimittaa ketjutuslogiikan ja sisältää säikeiden ajoituksen. JVM-ketjut ovat vihreät langat, tai käyttäjän ketjut.

JVM: n ketjun ajastin ajastaa vihreät säikeet etusijalle—Langan suhteellinen merkitys, jonka ilmaisette kokonaislukuna hyvin määritellystä arvoryhmästä. Tyypillisesti JVM: n ketjun ajastin valitsee korkeimman prioriteetin säikeen ja antaa kyseisen ketjun toimia, kunnes se joko päättyy tai estää. Silloin säikeiden ajoittaja valitsee seuraavaksi tärkeimmän prioriteetin. Kierre (yleensä) kulkee, kunnes se päättyy tai estää. Jos ketjun suorituksen aikana korkeamman prioriteetin säike estetään (ehkä korkeamman prioriteetin säikeen uniaika on päättynyt), ketjun ajastin yrittää, tai keskeyttää alemman prioriteetin langan ja määrittää estämättömän korkeamman prioriteetin langan prosessorille.

merkintä: Suoritettava säie, jolla on korkein prioriteetti, ei aina toimi. Tässä on Java-kielimääritys 'etusijalle:

Jokaisella langalla on etusijalle. Kun resurssien käsittelystä on kilpailua, korkeamman prioriteetin säikeet suoritetaan yleensä matalamman prioriteetin säikeiden sijasta. Tällainen etusija ei kuitenkaan takaa, että korkeimman prioriteetin säikeet ovat aina käynnissä, eikä langan prioriteetteja voida käyttää keskinäisen poissulkemisen luotettavaan toteuttamiseen.

Tämä myöntäminen kertoo paljon vihreän langan JVM: ien toteuttamisesta. Niillä JVM: llä ei ole varaa antaa ketjujen tukkeutua, koska se sitoisi JVM: n ainoan toteutuslangan. Siksi, kun ketjun on estettävä, esimerkiksi kun kyseinen ketju lukee dataa hitaasti saapuessaan tiedostosta, JVM saattaa pysäyttää ketjun suorituksen ja käyttää kyselymekanismia tietojen saapumisajan määrittämiseen. Säikeen pysyessä pysäytettynä JVM: n säikeiden ajoitin saattaa ajoittaa matalamman prioriteetin säikeen suoritettavaksi. Oletetaan, että tietoja saapuu, kun alemman prioriteetin säike on käynnissä. Vaikka korkeamman prioriteetin ketjun pitäisi toimia heti, kun tiedot saapuvat, se tapahtuu vasta, kun JVM seuraavaksi kysyy käyttöjärjestelmän ja huomaa saapumisen. Siksi matalamman prioriteetin säike kulkee, vaikka korkeamman prioriteetin säikeen pitäisi toimia. Sinun on huolehdittava tästä tilanteesta vain, kun tarvitset reaaliaikaista Java-käyttäytymistä. Mutta silloin Java ei ole reaaliaikainen käyttöjärjestelmä, joten miksi huolehtia?

Harkitse seuraavaa ymmärtääksesi, mistä juoksevasta vihreästä langasta tulee parhaillaan käynnissä oleva vihreä lanka. Oletetaan, että sovelluksessasi on kolme säiettä: pääkehys, joka suorittaa main () menetelmä, laskulanka ja ketju, joka lukee näppäimistön syötteen. Kun näppäimistön syötettä ei ole, lukulanka säilyy. Oletetaan, että lukulangalla on korkein prioriteetti ja laskulangalla on matalin prioriteetti. (Oletetaan yksinkertaisuuden vuoksi myös, että muita sisäisiä JVM-säikeitä ei ole saatavilla.) Kuva 1 havainnollistaa näiden kolmen ketjun suorittamista.

Aikana T0 päälanka alkaa toimia. Aikana T1 päälanka aloittaa laskennan. Koska laskulangalla on matalampi prioriteetti kuin pääkierteellä, laskulanka odottaa prosessoria. Aikana T2 päälanka aloittaa lukulangan. Koska lukulangalla on korkeampi prioriteetti kuin pääkierteellä, päälanka odottaa prosessoria lukulangan käydessä. Aikana T3 lukulanka estää ja pääkierre kulkee. Aikana T4 lukulanka vapautuu ja toimii; päälanka odottaa. Lopuksi ajankohtana T5 lukulankalohkot ja päälanka kulkevat. Tämä vuorottelu suorituksessa luku- ja pääsäikeiden välillä jatkuu niin kauan kuin ohjelma toimii. Laskulankaa ei koskaan suoriteta, koska sillä on alhaisin prioriteetti ja se nälkää prosessorin huomion vuoksi prosessorin nälkä.

Voimme muuttaa tätä skenaariota antamalla laskelangalle saman prioriteetin kuin pääkierre. Kuva 2 esittää tuloksen, joka alkaa ajasta T2. (Ennen T2: ta kuva 2 on identtinen kuvan 1 kanssa.)

Aikana T2 lukulanka jatkuu, kun pää- ja laskennankierteet odottavat prosessoria. Aikana T3 lukulankalohkot ja laskulanka kulkevat, koska päälanka juoksi juuri ennen lukulankaa. Aikana T4 lukulanka vapautuu ja toimii; pää- ja laskelangat odottavat. Aikana T5 lukulanka säilyy ja pääkierre kulkee, koska laskulanka meni juuri ennen lukulankaa. Tämä vuorottelu pää- ja laskelangan välillä jatkuu niin kauan kuin ohjelma on käynnissä ja riippuu korkeamman prioriteetin säikeiden käynnistä ja estämisestä.

Meidän on otettava huomioon viimeinen kohta vihreän säikeen ajoituksessa. Mitä tapahtuu, kun alemman prioriteetin säikeessä on lukko, jota korkeamman prioriteetin säike tarvitsee? Korkeamman prioriteetin säike estää, koska se ei voi saada lukitusta, mikä tarkoittaa, että korkeamman prioriteetin säikeellä on tosiasiallisesti sama prioriteetti kuin alemman prioriteetin säikeellä. Esimerkiksi prioriteetti 6 säie yrittää hankkia lukon, jolla prioriteetti 3 säie pitää. Koska prioriteettisäike 6 on odotettava, kunnes se voi hankkia lukituksen, prioriteettilanka 6 päättyy 3 prioriteettiin - ilmiö tunnetaan nimellä prioriteetin kääntäminen.

Prioriteetin kääntäminen voi viivästyttää huomattavasti korkeamman prioriteetin säikeen suorittamista. Oletetaan esimerkiksi, että sinulla on kolme ketjua, joiden prioriteetit ovat 3, 4 ja 9. Prioriteetin 3 ketju on käynnissä ja muut ketjut ovat estettyjä. Oletetaan, että prioriteettilanka 3 tarttuu lukkoon ja prioriteettilanka 4 avautuu. Prioriteettilangasta 4 tulee parhaillaan käynnissä oleva ketju. Koska prioriteettilanka 9 vaatii lukituksen, se odottaa edelleen, kunnes prioriteettilanka 3 vapauttaa lukon. Prioriteetin 3 säie ei kuitenkaan voi vapauttaa lukitusta, ennen kuin prioriteetti 4 säie estää tai päättyy. Tämän seurauksena prioriteettilanka 9 viivästyttää sen suorittamista.