Programmēšana

Java pavedienu programmēšana reālajā pasaulē, 1. daļa

Visas Java programmas, kas nav vienkāršas uz konsole balstītas lietojumprogrammas, ir vai nu ar daudzsavienojumu, neatkarīgi no tā, vai vēlaties vai nē. Problēma ir tā, ka Abstract Windowing Toolkit (AWT) apstrādā operētājsistēmas (OS) notikumus savā pavedienā, tāpēc jūsu klausītāja metodes faktiski darbojas uz AWT pavediena. Šīs pašas klausītāja metodes parasti piekļūst objektiem, kuriem piekļūst arī no galvenā pavediena. Šajā brīdī var būt vilinoši apglabāt galvu smiltīs un izlikties, ka jums nav jāuztraucas par diegu jautājumiem, taču parasti no tā nevarat tikt prom. Diemžēl praktiski nevienā no grāmatām par Java nav pietiekami padziļināti apskatīti pavedienu jautājumi. (Lai uzzinātu noderīgu grāmatu sarakstu par šo tēmu, skatiet resursus.)

Šis raksts ir pirmais sērijā, kas sniegs reālus risinājumus Java programmēšanas problēmām daudzjoslu vidē. Tas ir paredzēts Java programmētājiem, kuri saprot valodas lietas ( sinhronizēts atslēgvārdu un dažādas iespējas Vītne klase), bet vēlaties uzzināt, kā efektīvi izmantot šīs valodas funkcijas.

Atkarība no platformas

Diemžēl Java solījums par platformas neatkarību krīt uz sejas pavedienu arēnā. Lai gan ir iespējams rakstīt no platformas neatkarīgu daudzsavienojumu Java programmu, jums tas jādara ar atvērtām acīm. Tā īsti nav Java vaina; ir gandrīz neiespējami uzrakstīt patiesi no platformas neatkarīgu vītņu sistēmu. (Douga Šmita ACE [Adaptīvās komunikācijas vides] ietvars ir labs, lai arī sarežģīts mēģinājums. Saiti uz viņa programmu skatiet resursos.) Tātad, pirms es nākamajās daļās varu runāt par cietajiem Java programmēšanas jautājumiem, man apspriest grūtības, kuras rada platformas, kurās varētu darboties Java virtuālā mašīna (JVM).

Atomu enerģija

Pirmais OS līmeņa jēdziens, kas ir svarīgi saprast, ir atoms. Atomu darbību nevar pārtraukt ar citu pavedienu. Java patiešām nosaka vismaz dažas atomu operācijas. Jo īpaši piešķiršana jebkura veida mainīgajiem, izņemot ilgi vai dubultā ir atomu. Jums nav jāuztraucas par to, ka pavediens uzdevuma vidū aizliedz metodi. Praksē tas nozīmē, ka jums nekad nav jāinhronizē metode, kas neko nedara, bet atgriež vērtību (vai piešķir tai vērtību) būla vai int instances mainīgais. Tāpat nevajadzētu sinhronizēt metodi, kas daudz skaitļoja, izmantojot tikai lokālos mainīgos un argumentus un kura šīs pēdējās izdarīšanas rezultātā attiecināja aprēķina rezultātus uz instances mainīgo. Piemēram:

klase some_class {int daži_lauks; void f (some_class arg) // apzināti nav sinhronizēts {// Šeit dariet daudz lietu, kas izmanto lokālos mainīgos // un metožu argumentus, bet nepiekļūst // nevienam klases laukam (vai izsauc jebkuru metodi //, kas piekļūst jebkuram klases lauki). // ... daži_lauks = jauna_vērtība; // dari to pēdējo. }} 

No otras puses, izpildot x = ++ y vai x + = y, pēc palielināšanas, bet pirms uzdevuma veikšanas jūs varētu būt tiesīgs atteikties. Lai šajā situācijā iegūtu atomu, jums jāizmanto atslēgvārds sinhronizēts.

Tas viss ir svarīgi, jo sinhronizācijas pieskaitāmās izmaksas var būt nebūtiskas un var atšķirties atkarībā no OS. Nākamā programma parāda problēmu. Katra cilpa atkārtoti izsauc metodi, kas veic tās pašas darbības, bet vienu no metodēm (bloķēšana ()) tiek sinhronizēts, bet otrs (not_locking ()) nav. Izmantojot JDK "performance-pack" VM, kas darbojas operētājsistēmā Windows NT 4, programma ziņo par 1,2 sekunžu izpildlaika starpību starp abām cilpām vai aptuveni 1,2 mikrosekundes uz zvanu. Šī atšķirība, iespējams, nešķiet daudz, taču tā ir zvana laika pieaugums par 7,25 procentiem. Protams, procentuālais pieaugums atkrīt, jo metode strādā vairāk, taču ievērojams skaits metožu - vismaz manās programmās - ir tikai dažas koda rindiņas.

importēt java.util. *; klases sinhronizācija {  sinhronizēta int bloķēšana (int a, int b) {return a + b;} int not_locking (int a, int b) {return a + b;}  privāta statiska gala int ITERĀCIJAS = 1000000; static public void main (String [] args) {sinhronizācijas testeris = new synch (); double start = jauns datums (). getTime ();  testerim (ilgi i = ITERĀCIJAS; --i> = 0;). bloķēšana (0,0);  double end = jauns datums (). getTime (); dubultā bloķēšanas_laiks = beigas - sākums; sākums = jauns datums (). getTime ();  par (ilgi i = ITERĀCIJAS; --i> = 0;) tester.not_locking (0,0);  beigas = jauns datums (). getTime (); double not_locking_time = beigas - sākums; dubultā time_in_synchronization = locking_time - not_locking_time; System.out.println ("Sinhronizācijai zaudētais laiks (milis.):" + Time_in_synchronization); System.out.println ("Pieskaitāmo izmaksu bloķēšana katram zvanam:" + (laika_inhronizācija / ITERĀCIJAS)); System.out.println (not_locking_time / locking_time * 100.0 + "% pieaugums"); }} 

Lai gan HotSpot VM ir paredzēts risināt sinhronizācijas un pieskaitāmās problēmas, HotSpot nav freebee - jums tas ir jāpērk. Ja vien jūs licencējat un nepiegādājat HotSpot kopā ar savu lietotni, nav iespējams pateikt, kāds VM būs mērķa platformā, un, protams, vēlaties, lai pēc iespējas mazāk programmas izpildes ātruma būtu atkarīgs no VM, kas to izpilda. Pat ja strupceļa problēmas (par kurām es runāšu nākamajā šīs sērijas daļā) nepastāvēja, priekšstats, ka jums vajadzētu "visu sinhronizēt", ir vienkārši nepareizs.

Vienlaicība pret paralēlismu

Nākamais ar operētājsistēmu saistītais jautājums (un galvenā problēma, rakstot no platformas neatkarīgu Java) ir saistīta ar jēdzieniem vienlaikus un paralēlisms. Vienlaicīgas daudzsavienojumu sistēmas rada vairāku vienlaikus izpildāmu uzdevumu izskatu, taču šie uzdevumi faktiski tiek sadalīti gabalos, kas procesoru koplieto ar citu uzdevumu daļām. Šis attēls ilustrē problēmas. Paralēlās sistēmās faktiski vienlaikus tiek veikti divi uzdevumi. Paralēlumam nepieciešama vairāku procesoru sistēma.

Ja vien jūs netērējat daudz laika bloķētu laiku, gaidot I / O darbību pabeigšanu, programma, kas izmanto vairākus vienlaicīgus pavedienus, bieži darbosies lēnāk nekā līdzvērtīga viena pavediena programma, lai gan tā bieži būs labāk organizēta nekā līdzvērtīga viena -vītnes versija. Programma, kas izmanto vairākus pavedienus, kas darbojas paralēli vairākos procesoros, darbosies daudz ātrāk.

Lai gan Java ļauj pavedienus pilnībā ieviest VM, vismaz teorētiski, šī pieeja izslēdz jebkādu paralēlismu jūsu lietojumprogrammā. Ja netiktu izmantoti operētājsistēmas līmeņa pavedieni, OS skatītu VM instanci kā viena pavediena lietojumprogrammu, kas, visticamāk, būtu ieplānota vienam procesoram. Rezultāts būtu tāds, ka divi Java pavedieni, kas darbojas vienā un tajā pašā VM instancē, nekad nedarbotos paralēli, pat ja jums būtu vairāki CPU un jūsu VM būtu vienīgais aktīvais process. Protams, divi VM gadījumi, kuros darbojas atsevišķas lietojumprogrammas, protams, varētu darboties paralēli, taču es vēlos darīt labāk nekā tas. Lai iegūtu paralēlismu, VM jābūt kartē Java pavedienus līdz OS pavedieniem; Tātad, jūs nevarat atļauties ignorēt atšķirības starp dažādiem vītņu modeļiem, ja platformas neatkarība ir svarīga.

Precizējiet savas prioritātes

Es parādīšu, kā tikko apspriestie jautājumi var ietekmēt jūsu programmas, salīdzinot divas operētājsistēmas: Solaris un Windows NT.

Java vismaz teorētiski nodrošina desmit prioritāros līmeņus pavedieniem. (Ja divi vai vairāki pavedieni gaida palaišanu, tiks izpildīts viens ar augstāko prioritātes līmeni.) Solaris, kas atbalsta 231 prioritātes līmeni, tā nav problēma (lai gan Solaris prioritātes var būt sarežģīti izmantot - vairāk par šo pēc mirkļa). NT, savukārt, ir pieejami septiņi prioritārie līmeņi, un tie ir jāiekļauj Java desmit. Šī kartēšana nav definēta, tāpēc ir daudz iespēju. (Piemēram, Java 1. un 2. prioritātes līmenis var gan pieskarties NT 1. prioritātes līmenim, gan Java 8., 9. un 10. prioritātes līmenis var visu saistīt ar NT 7. līmeni.)

NT prioritāšu līmeņu mazums ir problēma, ja vēlaties izmantot prioritāti, lai kontrolētu plānošanu. Lietas vēl vairāk sarežģī fakts, ka prioritārie līmeņi nav fiksēti. NT nodrošina mehānismu, ko sauc prioritātes palielināšana, kuru var izslēgt ar C sistēmas zvanu, bet ne no Java. Ja ir iespējota prioritāšu palielināšana, NT palielina pavediena prioritāti ar nenoteiktu daudzumu uz nenoteiktu laiku katru reizi, kad tā izpilda noteiktus ar I / O saistītus sistēmas izsaukumus. Praksē tas nozīmē, ka pavediena prioritātes līmenis varētu būt augstāks, nekā jūs domājat, jo šis pavediens nejauši veica I / O darbību.

Prioritātes palielināšanas mērķis ir novērst to, ka pavedieni, kas veic fona apstrādi, neietekmētu UI lielo uzdevumu acīmredzamo atsaucību. Citās operētājsistēmās ir sarežģītāki algoritmi, kas parasti samazina fona procesu prioritāti. Šīs shēmas mīnuss, it īpaši, ja tā tiek ieviesta pa pavedieniem, nevis uz procesu, ir tā, ka ir ļoti grūti izmantot prioritāti, lai noteiktu, kad darbosies konkrēts pavediens.

Tas pasliktinās.

Solaris, tāpat kā visās Unix sistēmās, procesiem ir prioritāte, kā arī pavedieni. Augstas prioritātes procesu pavedienus nevar pārtraukt ar zemas prioritātes procesu pavedieniem. Turklāt sistēmas administrators var ierobežot noteikta procesa prioritāro līmeni, lai lietotāja process nepārtrauktu kritiskos OS procesus. NT neko no tā neatbalsta. NT process ir tikai adreses telpa. Tam nav prioritātes per se, un tas nav plānots. Sistēma ieplāno pavedienus; tad, ja dotā pavediens darbojas procesā, kura nav atmiņā, process tiek apmainīts. NT pavedienu prioritātes ietilpst dažādās "prioritāšu klasēs", kas tiek sadalītas pa faktisko prioritāšu nepārtrauktību. Sistēma izskatās šādi:

Kolonnas ir faktiskie prioritāšu līmeņi, un tikai 22 no tiem ir jāpiedalās visām lietojumprogrammām. (Pārējos lieto pats NT.) Rindas ir prioritārās klases. Pavedieni, kas darbojas procesā, kas ir piesaistīts dīkstāves prioritātes klasei, darbojas 1. līdz 6. un 15. līmenī atkarībā no tiem piešķirtā loģiskā prioritātes līmeņa. Procesa pavedieni, kas saistīti ar parasto prioritāšu klasi, darbosies 1., 6. līdz 10. vai 15. līmenī, ja procesam nav ievades fokusa. Ja tam ir ievades fokuss, pavedieni darbojas 1., 7. līdz 11. vai 15. līmenī. Tas nozīmē, ka tukšgaitas prioritātes klases procesa augstas prioritātes pavediens var novērst zemas prioritātes pavedienu parastā prioritārā klases procesā, bet tikai tad, ja šis process darbojas fonā. Ievērojiet, ka procesam, kas darbojas "augstas" prioritātes klasē, ir pieejami tikai seši prioritāšu līmeņi. Pārējām klasēm ir septiņas.

NT neparedz iespēju ierobežot procesa prioritāro klasi. Jebkurš pavediens jebkuram mašīnas procesam jebkurā laikā var pārņemt kastes kontroli, palielinot savu prioritātes klasi; pret to nav aizsardzības.

Tehniskais termins, ko izmantoju NT prioritātes aprakstam, ir nesvēts haoss. Praksē NT principā prioritāte ir praktiski nevērtīga.

Tātad, ko darīt programmētājs? Starp NT ierobežoto prioritāšu līmeņu skaitu un nekontrolējamo prioritāšu palielināšanu Java programmai nav absolūti droša veida, kā plānot prioritātes līmeņus. Viens efektīvs kompromiss ir ierobežot sevi Thread.MAX_PRIORITY, Vītne.MIN_PRIORITY, un Vītne.NORM_PRIORITY kad zvanāt setPriority (). Šis ierobežojums vismaz novērš problēmu, kas saistīta ar 10 līmeņu kartēšanu līdz 7 līmeņiem. Es domāju, ka jūs varētu izmantot os.name sistēmas rekvizīts, lai noteiktu NT, un pēc tam izsauciet vietējo metodi, lai izslēgtu prioritātes palielināšanu, taču tas nedarbosies, ja jūsu lietotne darbojas pārlūkprogrammā Internet Explorer, ja vien neizmantojat arī Sun VM spraudni. (Microsoft VM izmanto nestandarta vietējās metodes ieviešanu.) Jebkurā gadījumā es ienīstu vietējo metožu izmantošanu. Es parasti pēc iespējas izvairos no problēmas, ieliekot lielāko daļu pavedienu NORM_PRIORITY un izmantojot plānošanas mehānismus, kas nav prioritāri. (Es apspriedīšu dažus no tiem šīs sērijas turpmākajās daļās.)

Sadarboties!

Parasti operētājsistēmas atbalsta divus pavedienu modeļus: kooperatīvs un preventīvs.

Kooperatīvais daudzšķiedru modelis

Iekšā kooperatīvs sistēma, pavediens saglabā kontroli pār savu procesoru, līdz izlemj no tā atteikties (kas varētu būt nekad). Dažādiem pavedieniem ir jāsadarbojas savā starpā vai visi pavedieni, izņemot vienu, tiks "izsalkuši" (tas nozīmē, ka nekad nav dota iespēja palaist). Plānošana lielākajā daļā kooperatīvo sistēmu notiek stingri pēc prioritārā līmeņa. Kad pašreizējais pavediens atsakās no vadības, augstākās prioritātes gaidīšanas pavediens iegūst kontroli. (Izņēmums no šī noteikuma ir Windows 3.x, kas izmanto kooperatīvu modeli, bet nav daudz plānotāja. Logs, kuram ir fokuss, tiek kontrolēts.)

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