Programmēšana

Java 101: Java pavedienu izpratne, 1. daļa: Iepazīstināšana ar pavedieniem un palaižamajiem elementiem

Šis raksts ir pirmais četrās daļās Java 101 sērija, kurā tiek pētīti Java pavedieni. Lai gan jūs domājat, ka Java pavedienus ir grūti uztvert, es nodomāju jums parādīt, ka pavedieni ir viegli saprotami. Šajā rakstā es jūs iepazīstinu ar Java pavedieniem un palaistajiem. Turpmākajos rakstos mēs izpētīsim sinhronizāciju (izmantojot slēdzenes), sinhronizācijas problēmas (piemēram, strupceļu), gaidīšanas / paziņošanas mehānismu, plānošanu (ar prioritāti un bez tās), pavedienu pārtraukšanu, taimerus, svārstīgumu, pavedienu grupas un pavedienu lokālos mainīgos .

Ņemiet vērā, ka šis raksts (daļa no JavaWorld arhīva) 2013. gada maijā tika atjaunināts ar jauniem kodu sarakstiem un lejupielādējamu avota kodu.

Izpratne par Java pavedieniem - izlasiet visu sēriju

  • 1. daļa: Vītņu un skrējienu iepazīstināšana
  • 2. daļa: Sinhronizācija
  • 3. daļa: Vītnes plānošana un gaidīšana / paziņošana
  • 4. daļa: pavedienu grupas un nepastāvība

Kas ir pavediens?

Konceptuāli jēdziens a pavediens nav grūti aptvert: tas ir neatkarīgs izpildes ceļš, izmantojot programmas kodu. Veicot vairākus pavedienus, viena pavediena ceļš caur to pašu kodu parasti atšķiras no citiem. Piemēram, pieņemsim, ka viens pavediens izpilda if-else paziņojuma baita koda ekvivalentu ja daļa, bet cits pavediens izpilda baita koda ekvivalentu cits daļa. Kā JVM seko katra pavediena izpildei? JVM piešķir katram pavedienam savu metodi-izsaukuma kaudzīti. Papildus pašreizējās baitu koda instrukcijas izsekošanai, metodi izsaukuma kaudze izseko vietējos mainīgos, parametrus, kurus JVM nodod metodei, un metodes atgriešanās vērtību.

Kad vairāki pavedieni vienā un tajā pašā programmā izpilda baita koda instrukciju secības, šī darbība ir pazīstama kā daudzsavienojums. Vairākas vītnes palīdz programmai dažādos veidos:

  • Vairāku pavedienu GUI (grafiskā lietotāja saskarne) bāzes programmas paliek atsaucīgas lietotājiem, veicot citus uzdevumus, piemēram, pārveidojot dokumentu vai izdrukājot dokumentu.
  • Vītņotās programmas parasti tiek pabeigtas ātrāk nekā viņu bezvītņu programmas. Tas jo īpaši attiecas uz pavedieniem, kas darbojas ar daudzprocesoru mašīnu, kur katram pavedienam ir savs procesors.

Java caur to nodrošina daudzsavienojumu java.lang.Thread klasē. Katrs Vītne objekts apraksta vienu izpildes pavedienu. Šī izpilde notiek Vītne's palaist () metodi. Jo noklusējuma palaist () metode neko nedara, jums ir apakšklase Vītne un ignorēt palaist () paveikt noderīgu darbu. Lai iegūtu garšu pavedieniem un vairāku pavedienu kontekstā Vītne, pārbaudiet 1. sarakstu:

Saraksts 1. ThreadDemo.java

// ThreadDemo.java klase ThreadDemo {public static void main (String [] args) {MyThread mt = new MyThread (); mt.start (); priekš (int i = 0; i <50; i ++) System.out.println ("i =" + i + ", i * i =" + i * i); }} klase MyThread paplašina Thread {public void run () {for (int skaits = 1, rinda = 1; rinda <20; rinda ++, skaits ++) {par (int i = 0; i <skaits; i ++) System.out. druka ('*'); System.out.print ('\ n'); }}}

1. saraksts parāda pirmkodu lietojumprogrammai, kas sastāv no klasēm ThreadDemo un MyThread. Klase ThreadDemo vada lietojumprogrammu, izveidojot MyThread objektu, sākot pavedienu, kas asociējas ar šo objektu, un izpildot kodu, lai izdrukātu kvadrātu tabulu. Turpretī MyThread ignorē Vītne's palaist () metode taisnstūra trīsstūra, kas sastāv no zvaigznītes rakstzīmēm, drukāšanai (uz standarta izvades straumes).

Vītņu plānošana un JVM

Lielākā daļa (ja ne visas) JVM implementācijas izmanto pamatā esošās platformas vītņošanas iespējas. Tā kā šīs iespējas ir specifiskas platformai, jūsu daudzjoslu programmu izejas secība var atšķirties no kāda cita izejas secības. Šī atšķirība izriet no plānošanas, tēmas, kuru es izpētīju vēlāk šajā sērijā.

Kad rakstāt java ThreadDemo lai palaistu lietojumprogrammu, JVM izveido sākuma izpildes pavedienu, kas izpilda galvenais () metodi. Izpildot mt.start ();, sākuma pavediens liek JVM izveidot otru izpildes pavedienu, kas izpilda baita koda instrukcijas, kas satur MyThread objekta palaist () metodi. Kad sākt() metode atgriežas, sākuma pavediens izpilda to priekš cilpa, lai izdrukātu kvadrātu tabulu, savukārt jaunais pavediens izpilda palaist () metode taisnleņķa trīsstūra drukāšanai.

Kā izskatās izlaide? Palaist ThreadDemo lai uzzinātu. Jūs ievērosiet, ka katra pavediena izeja mēdz krustoties ar otra produkciju. Tas rodas tāpēc, ka abi pavedieni izvadi nosūta uz to pašu standarta izvades straumi.

Vītnes klase

Lai prasmīgi rakstītu daudzvītņotu kodu, vispirms ir jāsaprot dažādas metodes, kas veido kodu Vītne klasē. Šajā sadaļā ir izpētītas daudzas no šīm metodēm. Konkrētāk, jūs uzzināt par metodēm pavedienu sākšanai, pavedienu nosaukšanai, pavedienu iemidzināšanai, pavedienu dzēšanas noteikšanai, viena pavediena savienošanai ar citu pavedienu un visu aktīvo pavedienu uzskaitīšanai pašreizējā pavediena pavedienu grupā un apakšgrupās. Es arī apspriežos Vītneatkļūdošanas palīglīdzekļi un lietotāju pavedieni pret dēmonu pavedieniem.

Es iepazīstināšu ar pārējo Vītnemetodes nākamajos rakstos, izņemot Sun novecojušās metodes.

Novecojušas metodes

Saule ir novecojusi dažādas Vītne metodes, piemēram, apturēt () un turpināt(), jo tie var bloķēt jūsu programmas vai sabojāt objektus. Tā rezultātā jums nevajadzētu zvanīt viņiem savā kodā. Lai uzzinātu šo metožu risinājumus, skatiet SDK dokumentāciju. Es neietveru novecojušās metodes šajā sērijā.

Kontrolējošie pavedieni

Vītne ir astoņi konstruktori. Vienkāršākie ir:

  • Vītne (), kas rada a Vītne objekts ar noklusējuma nosaukumu
  • Vītne (virknes nosaukums), kas rada a Vītne objekts ar nosaukumu, kuru nosaukums arguments precizē

Nākamie vienkāršākie konstruktori ir Vītne (skrienams mērķis) un Vītne (skrienams mērķis, virknes nosaukums). Neatkarīgi no Skrienams parametriem šie konstruktori ir identiski iepriekšminētajiem konstruktoriem. Atšķirība: Skrienams parametri identificē objektus ārpusē Vītne kas nodrošina palaist () metodes. (Jūs uzzināt par Skrienams vēlāk šajā rakstā.) Pēdējie četri konstruktori ir līdzīgi Vītne (virknes nosaukums), Vītne (skrienams mērķis), un Vītne (skrienams mērķis, virknes nosaukums); tomēr gala konstruktoros ietilpst arī a ThreadGroup arguments organizatoriskiem mērķiem.

Viens no pēdējiem četriem konstruktoriem, Vītne (ThreadGroup grupa, Runnable mērķis, virknes nosaukums, garš stackSize), ir interesants ar to, ka ļauj norādīt vajadzīgo pavediena method-call stack lielumu. Spēja norādīt šo lielumu izrādās noderīga programmās ar metodēm, kas izmanto rekursiju - izpildes paņēmienu, ar kuru metode sevi atkārtoti sauc - eleganti atrisināt noteiktas problēmas. Nepārprotami nosakot kaudzes lielumu, dažreiz to var novērst StackOverflowErrors. Tomēr pārāk liels izmērs var izraisīt OutOfMemoryErrors. Arī Sun uzskata, ka metodes izsaukuma kaudzes lielums ir atkarīgs no platformas. Atkarībā no platformas, metodes zvana kaudzes lielums var mainīties. Tāpēc, pirms rakstāt kodu, kas izsauc, rūpīgi padomājiet par savas programmas sekām Vītne (ThreadGroup grupa, Runnable mērķis, virknes nosaukums, garš stackSize).

Sāciet savus transportlīdzekļus

Vītnes atgādina transportlīdzekļus: tās pārvieto programmas no sākuma līdz beigām. Vītne un Vītne apakšklases objekti nav pavedieni. Tā vietā viņi apraksta pavediena atribūtus, piemēram, tā nosaukumu, un satur kodu (izmantojot a palaist () metode), kuru pavediens izpilda. Kad pienāks laiks jauna pavediena izpildei palaist (), cits pavediens izsauc Vītnevai tā apakšklases objekts sākt() metodi. Piemēram, lai palaistu otro pavedienu, tiek izpildīts lietojumprogrammas sākuma pavediens galvenais ()- zvani sākt(). Atbildot uz to, JVM pavedienu apstrādes kods darbojas ar platformu, lai nodrošinātu, ka pavediens tiek pareizi inicializēts un izsauc a Vītnevai tā apakšklases objekts palaist () metodi.

Vienreiz sākt() pabeidz, izpilda vairākus pavedienus. Tā kā mums ir tendence domāt lineāri, mums bieži ir grūti saprast vienlaikus (vienlaicīga) darbība, kas notiek, kad darbojas divi vai vairāki pavedieni. Tāpēc jums jāpārbauda diagramma, kas parāda, kur pavediens tiek izpildīts (tā pozīcija) pret laiku. Zemāk redzamais attēls parāda šādu diagrammu.

Diagrammā ir parādīti vairāki nozīmīgi laika periodi:

  • Sākuma pavediena inicializēšana
  • Brīdis, kad šo pavedienu sāk izpildīt galvenais ()
  • Brīdis, kad šo pavedienu sāk izpildīt sākt()
  • Tas mirklis sākt() izveido jaunu pavedienu un atgriežas pie galvenais ()
  • Jaunā pavediena inicializēšana
  • Brīdī, kad jauno pavedienu sāk izpildīt palaist ()
  • Katra pavediena dažādie momenti beidzas

Ņemiet vērā, ka jaunā pavediena inicializācija, tā izpilde palaist (), un tā izbeigšana notiek vienlaikus ar sākuma pavediena izpildi. Ņemiet vērā arī to, ka pēc pavediena zvani sākt(), nākamie izsaukumi uz šo metodi pirms palaist () metode iziet no cēloņa sākt() iemest a java.lang.IllegalThreadStateException objekts.

Kas ir nosaukums?

Atkļūdošanas sesijas laikā ir noderīgi atšķirt vienu pavedienu no cita lietotājam draudzīgā veidā. Lai atšķirtu pavedienus, Java saista vārdu ar pavedienu. Šis nosaukums pēc noklusējuma ir Vītne, defise un vesels skaitlis, kura pamatā ir nulle. Jūs varat pieņemt Java noklusējuma pavedienu nosaukumus vai arī izvēlēties savus. Lai pielāgotu nosaukumus, Vītne nodrošina konstruktorus, kuri ņem nosaukums argumenti un a setName (virknes nosaukums) metodi. Vītne arī nodrošina a getName () metode, kas atgriež pašreizējo nosaukumu. 2. saraksts parāda, kā izveidot pielāgotu nosaukumu, izmantojot Vītne (virknes nosaukums) konstruktors un ielādējiet pašreizējo vārdu palaist () metodi, zvanot getName ():

Saraksts 2. NameThatThread.java

// NameThatThread.java klase NameThatThread {public static void main (String [] args) {MyThread mt; if (args.length == 0) mt = new MyThread (); else mt = new MyThread (args [0]); mt.start (); }} klase MyThread paplašina Thread {MyThread () {// Kompilators izveido super () baita koda ekvivalentu; } MyThread (virknes nosaukums) {super (nosaukums); // Pass name to Thread superclass} public void run () {System.out.println ("Mans vārds ir:" + getName ()); }}

Jūs varat nodot izvēles nosaukuma argumentu adresātam MyThread komandrindā. Piemēram, java NameThatThread X nodibina X kā pavediena nosaukums. Ja neizdodas norādīt vārdu, tiks parādīta šāda izeja:

Mani sauc: Thread-1

Ja vēlaties, varat mainīt super (vārds); zvaniet MyThread (virknes nosaukums) konstruktors uz zvanu setName (virknes nosaukums)-kā setName (nosaukums);. Šis pēdējais metodes izsaukums sasniedz to pašu mērķi - nosakot pavediena nosaukumu - kā super (vārds);. Es to atstāju kā vingrinājumu jums.

Galvenā nosaukšana

Java piešķir vārdu galvenais uz pavedienu, kas vada galvenais () metode, sākuma pavediens. Parasti šo vārdu redzat Izņēmums pavedienā "main" ziņojums, ka JVM noklusējuma izņēmumu apstrādātājs izdrukā, kad sākuma pavediens izmet izņēmuma objektu.

Gulēt vai negulēt

Vēlāk šajā slejā es jūs iepazīstināšu animācija- atkārtoti uz vienas virsmas zīmējot attēlus, kas nedaudz atšķiras viens no otra, lai panāktu kustību ilūziju. Lai veiktu animāciju, pavedienam ir jāpārtrauc divu secīgu attēlu parādīšana. Zvanīšana Vītneir statiska gulēt (ilgi milis) metode liek pavedienam apstāties milis milisekundes. Cits pavediens, iespējams, varētu pārtraukt miega pavedienu. Ja tas notiks, miega pavediens pamostas un iemet Pārtraukts izņēmums objekts no gulēt (ilgi milis) metodi. Tā rezultātā kods, kas zvana gulēt (ilgi milis) jāparādās a mēģiniet bloķēt - vai arī koda metodei jāietver Pārtraukts izņēmums tās metieni klauzula.

Demonstrēt gulēt (ilgi milis), Es esmu uzrakstījis CalcPI1 pieteikumu. Šī lietojumprogramma sāk jaunu pavedienu, kas izmanto matemātisko algoritmu, lai aprēķinātu matemātiskās konstantes pi vērtību. Kamēr jaunais pavediens aprēķina, sākuma pavediens tiek pārtraukts 10 milisekundes, zvanot gulēt (ilgi milis). Pēc sākuma pavediena pamodināšanas tas izdrukā pi vērtību, kuru jaunais pavediens saglabā mainīgajā pi. Uzskaitot 3 dāvanas CalcPI1avota kods:

Saraksts 3. CalcPI1.java

// CalcPI1.java klase CalcPI1 {public static void main (String [] args) {MyThread mt = new MyThread (); mt.start (); izmēģiniet {Thread.sleep (10); // Miega režīms 10 milisekundes} nozveja (InterruptedException e) {} System.out.println ("pi =" + mt.pi); }} klase MyThread paplašina pavedienu {Būla negatīvs = patiess; dubultā pi; // Inicializē uz 0.0, pēc noklusējuma public void run () {for (int i = 3; i <100000; i + = 2) {if (negatīvs) pi - = (1.0 / i); cits pi + = (1,0 / i); negatīvs =! negatīvs; } pi + = 1,0; pi * = 4,0; System.out.println ("Pabeigts aprēķināt PI"); }}

Ja palaidīsit šo programmu, redzēsit izvadi, kas ir līdzīgs (bet, iespējams, nav identisks) šādam:

pi = -0,2146197014017295 Pabeigts PI aprēķins
$config[zx-auto] not found$config[zx-overlay] not found