Programiranje

Programiranje niti Java v resničnem svetu, 1. del

Vsi programi Java, razen preprostih aplikacij na osnovi konzol, so večnitni, če vam je všeč ali ne. Težava je v tem, da Abstract Windowing Toolkit (AWT) obdeluje dogodke operacijskega sistema (OS) v lastni niti, zato se metode poslušalcev dejansko izvajajo v niti AWT. Iste metode poslušalcev običajno dostopajo do predmetov, do katerih je dostop tudi iz glavne niti. V tem trenutku je morda skušnjava, da si zakopljete glavo v pesek in se pretvarjate, da vam ni treba skrbeti zaradi težav z navojem, vendar se temu običajno ne morete izogniti. In na žalost tako rekoč nobena knjiga o Javi ne obravnava težav z nitmi dovolj poglobljeno. (Za seznam koristnih knjig na to temo glejte Viri.)

Ta članek je prvi v nizu, ki bo predstavil resnične rešitve problemov programiranja Jave v večnitnem okolju. Namenjen je programerjem Java, ki razumejo stvari na ravni jezika ( sinhronizirano ključna beseda in različne zmogljivosti Navoj razred), vendar se želite naučiti, kako učinkovito uporabljati te jezikovne funkcije.

Odvisnost od platforme

Na žalost obljuba Jave o neodvisnosti platforme v areni niti ne pade na površje. Čeprav je mogoče napisati večplastni program Java, neodvisen od platforme, morate to storiti z odprtimi očmi. Za to v resnici ni krivda Java; skoraj nemogoče je napisati resnično neodvisen od navojev sistem navojev. (Okvir ACE [Adaptive Communication Environment] Douga Schmidta je dober, čeprav zapleten poskus. Glejte Vire za povezavo do njegovega programa.) Torej, preden lahko v naslednjih delih govorim o temeljnih težavah s programiranjem Java, moram razpravljajte o težavah, ki jih povzročajo platforme, na katerih bi lahko deloval navidezni stroj Java (JVM).

Atomska energija

Prvi koncept na ravni OS, ki ga je pomembno razumeti, je atomskost. Atomske operacije ne more prekiniti druga nit. Java določa vsaj nekaj atomskih operacij. Zlasti dodelitev spremenljivkam katere koli vrste, razen dolga ali dvojno je atomska. Ni vam treba skrbeti, da bo nit sredi naloge predkupil metodo. V praksi to pomeni, da vam nikoli ni treba sinhronizirati metode, ki ne naredi ničesar, ampak vrne vrednost (ali ji dodeli vrednost) logično ali int spremenljivka primerka. Podobno tudi metode, ki je veliko izračunavala z uporabo samo lokalnih spremenljivk in argumentov in ki je rezultate tega izračuna dodelila spremenljivki primerka kot zadnjo stvar, ki jo je storila, ne bi bilo treba sinhronizirati. Na primer:

razred nekaj_razreda {int nekaj_polje; void f (some_class arg) // namerno ni sinhroniziran {// Tukaj naredi veliko stvari, ki uporabljajo lokalne spremenljivke // in argumente metode, vendar ne dostopa // do nobenega polja razreda (ali pokliče nobenih metod //, ki dostopajo do katerega koli polja razreda). // ... nekaj_polja = nova_vrednost; // to naredi zadnji. }} 

Po drugi strani pa pri izvrševanju x = ++ y ali x + = y, lahko ste preseženi po prirastku, vendar pred dodelitvijo. Če želite v tej situaciji dobiti atomskost, boste morali uporabiti ključno besedo sinhronizirano.

Vse to je pomembno, ker so splošni stroški sinhronizacije lahko netrivialni in se lahko razlikujejo od OS do OS. Naslednji program prikazuje težavo. Vsaka zanka ponavljajoče kliče metodo, ki izvaja enake operacije, vendar eno od metod (zaklepanje ()) je sinhroniziran, drugi (not_locking ()) ni. Z uporabo JMK "performance-pack" VM, ki se izvaja pod operacijskim sistemom Windows NT 4, program poroča o 1,2-sekundni razliki med izvajanjem obeh zank ali približno 1,2 mikrosekunde na klic. Ta razlika se morda ne zdi velika, vendar predstavlja 7,25-odstotno povečanje časa klica. Seveda odstotek povečanja pade, saj metoda opravi več dela, vendar je veliko metod - vsaj v mojih programih - le nekaj vrstic kode.

uvoz java.util. *; sinhronizacija razreda {  sinhronizirano zaklepanje int (int a, int b) {return a + b;} int not_locking (int a, int b) {return a + b;}  zasebni statični končni int ITERATIONS = 1000000; statična javna void main (String [] args) {tester sinhronizacije = novo sinhronizacijo (); dvojni začetek = nov datum (). getTime ();  for (long i = ITERATIONS; --i> = 0;) tester.locking (0,0);  dvojni konec = nov datum (). getTime (); dvojno zaklepanje_čas = konec - začetek; start = nov datum (). getTime ();  for (long i = ITERATIONS; --i> = 0;) tester.not_locking (0,0);  konec = nov datum (). getTime (); dvojno not_locking_time = konec - začetek; double time_in_synchronization = čas zaklepanja - čas ne_zaklepanja; System.out.println ("Čas, izgubljen za sinhronizacijo (v milis.):" + Čas_v_sinhronizaciji); System.out.println ("Zaklepanje režijskih stroškov na klic:" + (time_in_synchronization / ITERATIONS)); System.out.println (not_locking_time / locking_time * 100,0 + "% povečanje"); }} 

Čeprav naj bi VM HotSpot odpravil težavo s sinhronizacijo in režijo, HotSpot ni prosta čebela - kupiti jo morate. Če ne licencirate in ne oddate HotSpota s svojo aplikacijo, ni mogoče razbrati, kakšen VM bo na ciljni platformi, in seveda želite, da je čim manjša hitrost izvajanja vašega programa odvisna od VM, ki ga izvaja. Tudi če težave z zastoji (o katerih bom razpravljal v naslednjem delu te serije) ne bi obstajale, je pojem, da bi morali "vse sinhronizirati", povsem napačen.

Sočasnost v primerjavi z vzporednostjo

Naslednja težava, povezana z operacijskim sistemom (in glavna težava pri pisanju od platforme neodvisne Jave), je povezana s pojmi sočasnost in vzporednost. Sočasni večnitni sistemi dajejo videz več nalog, ki se izvajajo hkrati, vendar so te naloge dejansko razdeljene na koščke, ki delijo procesor s kosi drugih nalog. Naslednja slika prikazuje težave. V vzporednih sistemih se dve nalogi dejansko izvajata hkrati. Vzporednost zahteva sistem z več CPE.

Če ne porabite veliko časa blokirano in čakate na dokončanje V / I operacij, bo program, ki uporablja več sočasnih niti, pogosto deloval počasneje kot enakovreden enonitni program, čeprav bo pogosto bolje organiziran kot enakovreden posamezen -različica niti. Program, ki uporablja več niti, ki se vzporedno izvajajo na več procesorjih, bo deloval veliko hitreje.

Čeprav Java dovoljuje, da se navoji v celoti izvajajo v VM, vsaj teoretično ta pristop izključuje kakršno koli vzporednost v vaši aplikaciji. Če ne bi uporabili niti na ravni operacijskega sistema, bi OS videl primerek VM kot enonitno aplikacijo, ki bi bila najverjetneje razporejena na en procesor. Čisti rezultat bi bil, da se niti dve niti Java, ki bi se izvajali pod istim primerkom VM, ne bi nikoli izvajali vzporedno, tudi če bi imeli več CPU-jev in bi bil vaš VM edini aktiven postopek. Seveda bi se lahko dva primerka VM, ki bi izvajala ločene aplikacije, izvajala vzporedno, vendar želim narediti boljše od tega. Da bi dobili vzporednost, VM mora preslikava niti Java v niti OS; torej si ne morete privoščiti prezrtja razlik med različnimi modeli navojev, če je neodvisnost platforme pomembna.

Postavite svoje prioritete naravnost

Predstavil bom, kako lahko vprašanja, o katerih sem pravkar razpravljal, vplivajo na vaše programe s primerjavo dveh operacijskih sistemov: Solaris in Windows NT.

Java vsaj v teoriji zagotavlja deset prednostnih ravni za niti. (Če dve ali več niti čakata na zagon, se bo izvedla tista z najvišjo stopnjo prioritete.) V Solarisu, ki podpira 231 prednostnih stopenj, to ni problem (čeprav so prioritete Solarisa lahko zapletene - več o tem v trenutku). NT pa ima na voljo sedem prednostnih stopenj, ki jih je treba preslikati v deset Javine. To preslikavo ni opredeljeno, zato se ponuja veliko možnosti. (Na primer, prednostni ravni Java 1 in 2 se lahko preslikata na raven prioritete NT 1, stopnje prioritete Java 8, 9 in 10 pa se lahko preslikajo na stopnjo NT 7.)

Premajhna prednostna raven NT je težava, če želite prednost uporabljati za nadzor razporejanja. Stvari se še bolj zapletejo zaradi dejstva, da ravni prednostnih nalog niso določene. NT ponuja mehanizem, imenovan povečanje prioritete, ki ga lahko izklopite s sistemskim klicem C, ne pa tudi z Jave. Ko je omogočeno povečanje prioritete, NT vsakič, ko izvede določene sistemsko klice, povezane z V / I, poveča prioritet niti za nedoločen znesek. V praksi to pomeni, da bi lahko bila prednostna nit niti višja, kot si mislite, ker je ta nit v neprijetnem času izvedla operacijo V / I.

Bistvo povečanja prioritete je preprečiti, da bi niti, ki izvajajo obdelavo v ozadju, vplivale na navidezno odzivnost nalog, težkih do uporabniškega vmesnika. Drugi operacijski sistemi imajo bolj izpopolnjene algoritme, ki običajno znižujejo prednost procesov v ozadju. Slaba stran te sheme, zlasti kadar se izvaja na nit niti namesto na ravni procesa, je, da je zelo težko uporabiti prednost za določitev, kdaj bo določena nit delovala.

Vse slabše je.

Tako kot v vseh sistemih Unix imajo v Solarisu prednost postopki in niti. Niti visoko prioritetnih procesov niti nizko prioritetnih procesov ne morejo prekiniti. Poleg tega lahko sistemski skrbnik omeji prednostno stopnjo danega procesa, tako da uporabniški proces ne bo motil kritičnih procesov OS. NT nič od tega ne podpira. NT postopek je le naslovni prostor. Sam po sebi nima prioritete in ni razporejen. Sistem razporeja niti; nato pa, če se določena nit izvaja v procesu, ki ni v pomnilniku, se postopek zamenja. Prednosti niti NT spadajo v različne "razrede prednostnih nalog", ki so porazdeljene po kontinuumu dejanskih prioritet. Sistem je videti takole:

Stolpci so dejanske prednostne ravni, od katerih jih morajo vse aplikacije deliti le 22. (Ostale uporablja NT sam.) Vrstice so prednostni razredi. Niti, ki se izvajajo v postopku, ki je vezan na razred nedejavne prioritete, se izvajajo na stopnjah od 1 do 6 in 15, odvisno od dodeljene logične ravni prednosti. Niti procesa, ki je vezan kot običajni prioritetni razred, se bodo izvajale na stopnjah 1, 6 do 10 ali 15, če proces nima vhodnega fokusa. Če ima vhodno žarišče, se niti izvajajo na stopnjah 1, 7 do 11 ali 15. To pomeni, da lahko nit z visoko prioriteto v stanju mirovanja v prednostnem razredu prepreči nit z nizko prioriteto v postopku običajnega razreda prioritet, vendar le, če se ta postopek izvaja v ozadju. Upoštevajte, da ima postopek, ki se izvaja v razredu "visoke" prioritete, na voljo le šest prednostnih stopenj. Ostali razredi imajo sedem.

NT ne omogoča omejevanja prednostnega razreda procesa. Vsaka nit v katerem koli postopku na stroju lahko kadar koli prevzame nadzor nad poljem s povečanjem lastnega razreda prednosti; proti temu ni obrambe.

Tehnični izraz, ki ga uporabljam za opis prednostne naloge NT, je nesveti nered. V praksi je v NT prednost skoraj brez vrednosti.

Torej, kaj naj naredi programer? Med omejenim številom prednostnih stopenj NT in neobvladljivim povečevanjem prednostnih nalog ni nobenega popolnoma varnega načina, da bi program Java uporabil prednostne ravni za razporejanje. Izvedljiv kompromis je, da se omejite Nit.MAX_PRIORITY, Nit.MIN_PRIORITY, in Nit.NORM_PRIORITY ko pokličete setPriority (). Ta omejitev se izogne ​​vsaj težavam, ki so preslikane na 10 nivojev. Predvidevam, da bi lahko uporabili os.name sistemsko lastnost za zaznavanje NT in nato pokličite izvorno metodo za izklop povečanja prednosti, vendar to ne bo delovalo, če se vaša aplikacija izvaja v Internet Explorerju, razen če uporabljate tudi Sun-ov vtičnik VM. (Microsoftova VM uporablja nestandardno implementacijo izvorne metode.) V vsakem primeru sovražim uporabo izvornih metod. Težavi se ponavadi čim bolj izognem tako, da vstavim večino niti NORM_PRIORITY in z uporabo mehanizmov razporejanja, ki niso prednostni. (O nekaterih od njih bom razpravljal v prihodnjih delih te serije.)

Sodelujte!

Običajno obstajata dva modela navojev, ki ju podpirata operacijski sistem: kooperativni in preventivni.

Kooperativni večnitni model

V zadruga sistem ohrani nadzor nad svojim procesorjem, dokler se ne odloči, da se mu bo odrekel (kar morda ne bo nikoli). Različne niti morajo medsebojno sodelovati, sicer bodo vse niti, razen ene, "izstradane" (kar pomeni, da nikoli ne bodo imele možnosti za zagon). Razporejanje v večini zadružnih sistemov poteka strogo po prednostni ravni. Ko trenutna nit opusti nadzor, čakajoča nit z najvišjo prioriteto dobi nadzor. (Izjema od tega pravila je Windows 3.x, ki uporablja model sodelovanja, vendar nima veliko razporejevalnika. Okno, ki ima fokus, dobi nadzor.)

$config[zx-auto] not found$config[zx-overlay] not found