Programavimas

„Java 101“: „Java“ gijų supratimas. 1 dalis. Pristatome gijas ir paleidžiamas programas

Šis straipsnis yra pirmasis iš keturių dalių „Java 101“ serija, tyrinėjanti „Java“ gijas. Nors manote, kad „Java“ siūlų įsisavinimas bus sunkiai suvokiamas, ketinu parodyti, kad temas lengva suprasti. Šiame straipsnyje aš supažindinu jus su „Java“ gijomis ir paleidžiamomis programomis. Kituose straipsniuose nagrinėsime sinchronizavimą (naudojant užraktus), sinchronizavimo problemas (pvz., Aklavietę), laukimo / pranešimo mechanizmą, planavimą (su prioritetu ir be jo), gijų pertraukimą, laikmačius, nepastovumą, gijų grupes ir vietinius gijų kintamuosius. .

Atkreipkite dėmesį, kad šis straipsnis („JavaWorld“ archyvų dalis) buvo atnaujintas naujais kodų sąrašais ir atsisiunčiamu šaltinio kodu 2013 m. Gegužės mėn.

Suprasti „Java“ gijas - perskaitykite visą seriją

  • 1 dalis. Pristatome siūlus ir bėgimus
  • 2 dalis. Sinchronizavimas
  • 3 dalis: Gijų planavimas ir laukimas / pranešimas
  • 4 dalis. Siūlų grupės ir nepastovumas

Kas yra gija?

Konceptualiai a siūlas nėra sunku suvokti: tai yra nepriklausomas vykdymo kelias naudojant programos kodą. Kai vykdomos kelios gijos, vienos gijos kelias per tą patį kodą paprastai skiriasi nuo kitų. Pvz., Tarkime, kad viena gija vykdo „if-else“ sakinio baito kodo atitikmenį jei dalis, o kita gija vykdo baito kodo atitikmenį Kitas dalis. Kaip JVM seka kiekvienos gijos vykdymą? JVM suteikia kiekvienai gijai savo metodo skambučio kaminą. Be to, kad būtų galima stebėti dabartinę baito kodo instrukciją, metodo skambučio seka stebi vietinius kintamuosius, parametrus, kuriuos JVM perduoda metodui, ir metodo grąžinimo vertę.

Kai toje pačioje programoje kelios gijos vykdo baito kodo instrukcijų sekas, tas veiksmas yra žinomas kaip daugiasluoksnis. Daugialypis gavimas yra naudingas programai įvairiais būdais:

  • Daugiagijės GUI (grafinės vartotojo sąsajos) pagrindu sukurtos programos išlieka atsakingos vartotojams, atliekant kitas užduotis, pavyzdžiui, perrašant dokumentą ar spausdinant dokumentą.
  • Srieginės programos paprastai baigiamos greičiau nei jų be gijų. Tai ypač pasakytina apie gijas, veikiančias daugiaprocesorinėje mašinoje, kur kiekviena gija turi savo procesorių.

„Java“ per daugelį gijų persijungia per ją java.lang.Twread klasė. Kiekvienas Siūlas objektas apibūdina vieną vykdymo giją. Ta egzekucija įvyksta Siūlas's paleisti () metodas. Nes numatytasis paleisti () metodas nieko nedaro, turite subklasę Siūlas ir nepaisyti paleisti () atlikti naudingą darbą. Norint paragauti siūlų ir daugiasriegių siūlų Siūlas, išnagrinėkite 1 sąrašą:

Sąrašas 1. ThreadDemo.java

// ThreadDemo.java klasė 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); }} klasė „MyThread“ pratęsia „Thread“ {public void run () {for (int count = 1, row = 1; row <20; row ++, count ++) {for (int i = 0; i <count; i ++) System.out. spausdinti ('*'); System.out.print ('\ n'); }}}

1 sąraše pateikiamas programos, susidedančios iš klasių, šaltinio kodas „ThreadDemo“ ir „MyThread“. Klasė „ThreadDemo“ valdo programą sukurdamas „MyThread“ objektą, pradedant giją, susiejančią su tuo objektu, ir vykdant tam tikrą kodą, kad būtų atspausdinta kvadratų lentelė. Priešingai, „MyThread“ nepaiso Siūlas's paleisti () metodas atspausdinti (ant standartinės išvesties srauto) stačiakampio trikampio, susidedančio iš žvaigždutės ženklų.

Gijos planavimas ir JVM

Dauguma (jei ne visi) JVM diegimai naudoja pagrindinės platformos siūlų gavimo galimybes. Kadangi šios galimybės priklauso nuo platformos, jūsų daugiasluoksnių programų išvesties tvarka gali skirtis nuo kažkieno išvesties tvarkos. Šis skirtumas atsiranda dėl planavimo, temos, kurią nagrinėsiu vėliau šioje serijoje.

Kai rašote java ThreadDemo Norėdami paleisti programą, JVM sukuria pradinę vykdymo giją, kuri vykdo pagrindinis () metodas. Vykdant mt.start ();, pradinė gija liepia JVM sukurti antrą vykdymo giją, kuri vykdo baito kodo instrukcijas, sudarytas iš „MyThread“ objekto paleisti () metodas. Kai pradžia () metodas grįžta, pradinė gija vykdo jį dėl kilpa kvadratų lentelei atspausdinti, o nauja gija vykdo paleisti () stačiakampio trikampio atspausdinimo metodas.

Kaip atrodo išvestis? Bėk „ThreadDemo“ sužinoti. Jūs pastebėsite, kad kiekvienos gijos išvestys linkusios kirstis su kitos išvestimis. Taip yra todėl, kad abi gijos siunčia savo išvestį į tą patį standartinį išvesties srautą.

Siūlų klasė

Norėdami mokėti rašyti daugiasluoksnį kodą, pirmiausia turite suprasti įvairius metodus, kurie sudaro Siūlas klasė. Šiame skyriuje nagrinėjama daug tų metodų. Konkrečiai, jūs sužinosite apie gijų paleidimo, gijų pavadinimo, gijų užmigdymo, nustatymo, ar gija yra gyva, sujungimą su viena gija su kita gija ir visų dabartinės gijų grupės ir pogrupių aktyvių gijų išvardijimą. Aš taip pat diskutuoju Siūlasderinimo pagalbinės priemonės ir vartotojo gijos, palyginti su „daemon“ gijomis.

Aš pateiksiu likusią Siūlasmetodus tolesniuose straipsniuose, išskyrus „Sun“ nebenaudojamus metodus.

Nebenaudojami metodai

Saulė nebenaudojo įvairių Siūlas metodai, tokie kaip sustabdyti () ir tęsti(), nes jie gali užrakinti jūsų programas arba sugadinti objektus. Todėl neturėtumėte jų kviesti į savo kodą. Norėdami sužinoti apie tuos metodus, skaitykite SDK dokumentaciją. Šioje serijoje nenagrinėju nebenaudojamų metodų.

Konstruojantys siūlai

Siūlas turi aštuonis konstruktorius. Paprasčiausi yra:

  • Gija (), kuris sukuria Siūlas objektas su numatytuoju pavadinimu
  • Gija (eilutės pavadinimas), kuris sukuria Siūlas objektas su pavadinimu, kurį vardas argumentas nurodo

Kiti paprasčiausi konstruktoriai yra Siūlas (paleidžiamas taikinys) ir Gija (vykdomas taikinys, stygos pavadinimas). Be Bėgama parametrai, tie konstruktoriai yra identiški minėtiems konstruktoriams. Skirtumas: Bėgama parametrai identifikuoja objektus išorėje Siūlas kurie teikia paleisti () metodai. (Jūs sužinosite apie Bėgama vėliau šiame straipsnyje.) Paskutiniai keturi konstruktoriai yra panašūs Gija (eilutės pavadinimas), Siūlas (paleidžiamas taikinys)ir Gija (vykdomas taikinys, stygos pavadinimas); tačiau galutiniai konstruktoriai taip pat apima a „ThreadGroup“ argumentas organizaciniais tikslais.

Vienas iš keturių paskutinių konstruktorių, Siūlas („ThreadGroup“ grupė, „Runnable target“, „String“ pavadinimas, „long stackSize“), yra įdomus tuo, kad leidžia nurodyti norimą gijos metodo-iškvietimo kamino dydį. Gebėjimas nurodyti šį dydį yra naudingas programose, kurių metodai naudoja rekursiją - vykdymo techniką, kai metodas pakartotinai vadinasi - elegantiškai išspręsti tam tikras problemas. Aiškiai nustatydami kamino dydį, kartais galite jo išvengti „StackOverflowError“s. Tačiau gali atsirasti per didelis dydis „OutOfMemoryError“s. Be to, „Sun“ laiko metodo skambučio kamino dydį priklausomu nuo platformos. Priklausomai nuo platformos, metodo iškvietimo kamino dydis gali pasikeisti. Todėl prieš rašydami skambinantį kodą, gerai pagalvokite apie savo programos pasekmes Siūlas („ThreadGroup“ grupė, „Runnable target“, „String“ pavadinimas, „long stackSize“).

Užveskite savo transporto priemones

Siūlai panašūs į transporto priemones: jie juda programas nuo pradžios iki pabaigos. Siūlas ir Siūlas poklasio objektai nėra gijos. Vietoj to, jie apibūdina gijos atributus, tokius kaip jos pavadinimas, ir juose yra kodas (per paleisti () metodas), kurį gija vykdo. Kai ateis laikas vykdyti naują giją paleisti (), kita gija vadina Siūlasar jo poklasio objektas pradžia () metodas. Pavyzdžiui, norint paleisti antrą giją, programos pradinė gija, kuri vykdoma pagrindinis ()—Skambina pradžia (). Atsakydamas, JVM gijų tvarkymo kodas veikia su platforma, kad užtikrintų, jog gija tinkamai inicijuojama, ir iškviečia a Siūlasar jo poklasio objektas paleisti () metodas.

Kartą pradžia () baigia, vykdo kelias gijas. Kadangi mes esame linkę mąstyti tiesiškai, mums dažnai sunku suprasti kartu (vienu metu) veikla, kuri vyksta, kai veikia dvi ar daugiau gijų. Todėl turėtumėte ištirti diagramą, kurioje parodyta, kur gija vykdo (jos padėtis), palyginti su laiku. Žemiau pateiktame paveiksle pateikiama tokia diagrama.

Diagramoje parodyti keli reikšmingi laikotarpiai:

  • Pradinės gijos inicializavimas
  • Tą akimirką, kai ta gija pradedama vykdyti pagrindinis ()
  • Tą akimirką, kai ta gija pradedama vykdyti pradžia ()
  • Akimirka pradžia () sukuria naują giją ir grįžta į pagrindinis ()
  • Naujos gijos inicijavimas
  • Tą akimirką, kai pradedama vykdyti nauja gija paleisti ()
  • Skirtingi kiekvienos gijos momentai baigiasi

Atkreipkite dėmesį, kad naujos gijos inicijavimas, jos vykdymas paleisti ()ir jo nutraukimas įvyksta vienu metu su pradinės gijos vykdymu. Taip pat atkreipkite dėmesį, kad po pokalbio pokalbio pradžia (), paskesni raginimai tuo metodu prieš paleisti () metodas išeina iš priežasties pradžia () mesti a java.lang.IllegalThreadStateException objektas.

Kas varde?

Derinimo sesijos metu yra naudinga atskirti vieną giją nuo kitos vartotojui patogiu būdu. Norėdama atskirti gijas, „Java“ susieja vardą su gija. Šis vardas yra numatytasis Siūlas, brūkšnelio simbolis ir nulinis sveikasis skaičius. Galite priimti numatytuosius „Java“ gijų pavadinimus arba pasirinkti savo. Norėdami pritaikyti pasirinktus pavadinimus, Siūlas pateikia konstruktoriai, kurie imasi vardas argumentai ir a setName (eilutės pavadinimas) metodas. Siūlas taip pat numato a getName () metodas, kuris grąžina dabartinį pavadinimą. 2 sąrašas parodo, kaip nustatyti pasirinktinį pavadinimą naudojant Gija (eilutės pavadinimas) konstruktorius ir atkurkite dabartinį vardą paleisti () metodas skambindamas getName ():

Sąrašas 2. NameThatThread.java

// NameThatThread.java klasė NameThatThread {public static void main (String [] args) {MyThread mt; if (args.length == 0) mt = new MyThread (); else mt = nauja „MyThread“ (argumentai [0]); mt.start (); }} klasė „MyThread“ pratęsia giją {MyThread () {// Kompiliatorius sukuria baito kodo ekvivalentą super (); } „MyThread“ (eilutės pavadinimas) {super (vardas); // perduoti vardą „Thread superclass“} public void run () {System.out.println ("Mano vardas yra:" + getName ()); }}

Galite perduoti pasirinktinį pavadinimo argumentą „MyThread“ komandinėje eilutėje. Pavyzdžiui, java NameThatThread X nustato X kaip gijos pavadinimas. Jei nepavyksta nurodyti pavadinimo, pamatysite šį išvestį:

Mano vardas yra: „Thread-1“

Jei norite, galite pakeisti super (vardas); paskambinkite „MyThread“ (eilutės pavadinimas) konstruktorius skambučiui setName (eilutės pavadinimas)—Kaip setName (vardas);. Pastaruoju metodo iškvietimu pasiekiamas tas pats tikslas - nustatomas gijos pavadinimas - kaip super (vardas);. Palieku tai kaip pratimą jums.

Pavadinimas pagrindinis

„Java“ priskiria vardą pagrindinis prie gijos, kuria eina pagrindinis () metodas, pradinė gija. Paprastai tą vardą matote Išimtis temoje „pagrindinis“ pranešimas, kurį JVM numatytasis išimčių tvarkytuvas spausdina, kai pradinė gija išmeta išimties objektą.

Miegoti ar nemiegoti

Vėliau šioje skiltyje aš jus supažindinsiu animacija- pakartotinai piešdami ant vieno paviršiaus šiek tiek besiskiriančius vaizdus, ​​kad pasiektumėte judesio iliuziją. Norint atlikti animaciją, gija turi pristabdyti rodydama du iš eilės atvaizdus. Skambinama Siūlasstatinis miegas (ilgi milis) metodas priverčia giją pristabdyti milis milisekundės. Kita gija gali nutraukti miegantį siūlą. Jei taip atsitiks, miego siūlas pabunda ir išmeta Nutraukta išimtis objektas iš miegas (ilgi milis) metodas. Todėl kodas, kuris skambina miegas (ilgi milis) turi pasirodyti per bandyti blokas - arba kodo metodas turi būti įtrauktas Nutraukta išimtis jos metimai sąlyga.

Demonstruoti miegas (ilgi milis), Aš parašiau a CalcPI1 taikymas. Ta programa paleidžia naują giją, kuri naudoja matematinį algoritmą matematinės konstantos pi vertei apskaičiuoti. Kol apskaičiuojama nauja gija, pradinė gija pristabdoma 10 milisekundžių skambinant miegas (ilgi milis). Pabudęs pradinis siūlas, jis išspausdina pi vertę, kurią naujas siūlas saugo kintamajame pi. Išvardijamos 3 dovanos CalcPI1šaltinio kodas:

Sąrašas 3. CalcPI1.java

// CalcPI1.java klasė CalcPI1 {public static void main (String [] args) {MyThread mt = new MyThread (); mt.start (); pabandykite {Thread.sleep (10); // Miego laikas 10 milisekundžių} pagauti (InterruptedException e) {} System.out.println ("pi =" + mt.pi); }} klasė „MyThread“ pratęsia giją {loginis neiginys = tiesa; dvigubas pi; // Inicializuojama į 0.0, pagal numatytuosius nustatymus public void run () {for (int i = 3; i <100000; i + = 2) {if (neigiama) pi - = (1,0 / i); dar pi + = (1,0 / i); neigiamas =! neigiamas; } pi + = 1,0; pi * = 4,0; System.out.println ("Baigta skaičiuoti PI"); }}

Jei paleisite šią programą, pamatysite išvestį, panašią (bet tikriausiai ne tapačią):

pi = -0,2146197014017295 Baigtas skaičiuoti PI