Š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ītne
atkļūdošanas palīglīdzekļi un lietotāju pavedieni pret dēmonu pavedieniem.
Es iepazīstināšu ar pārējo Vītne
metodes 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 aVītne
objekts ar noklusējuma nosaukumuVītne (virknes nosaukums)
, kas rada aVītne
objekts ar nosaukumu, kurunosaukums
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 StackOverflowError
s. Tomēr pārāk liels izmērs var izraisīt OutOfMemoryError
s. 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ītne
vai 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ītne
vai 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 piegalvenais ()
- 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ītne
ir 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 CalcPI1
avota 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