9 klaidos, kurių reikia vengti programuojant "Java" kalba
Kokių klaidų reikėtų vengti programuojant "Java" kalba? Šiame straipsnyje atsakysime į šį klausimą.
Perskaitykite pirmąją mūsų tinklaraščio serijos, skirtos "Java" lygiagretinimui, dalį. Šiame straipsnyje plačiau apžvelgsime skirtumus tarp gijų ir procesų, gijų baseinus, vykdytojus ir dar daugiau!
Apskritai įprastinis programavimo metodas yra nuoseklus. Programoje viskas vyksta po vieną žingsnį.
Tačiau iš tiesų lygiagrečiai veikia visas pasaulis - tai gebėjimas vienu metu atlikti daugiau nei vieną užduotį.
Aptarti tokias pažangias temas kaip lygiagretumas Java arba daugiajutiklį, turime susitarti dėl bendrų apibrėžčių, kad būtume tikri, jog sutinkame tą patį.
Pradėkime nuo pagrindinių dalykų. Nenuosekliajame pasaulyje turime dviejų rūšių lygiagretumo atstovus: procesus ir
siūlai. Procesas yra vykdomos programos egzempliorius. Paprastai jis yra izoliuotas nuo kitų procesų.
Operacinė sistema yra atsakinga už išteklių priskyrimą kiekvienam procesui. Be to, ji veikia kaip laidininkas, kuris
sudaro tvarkaraščius ir juos kontroliuoja.
Siūlai yra tam tikros rūšies procesas, tačiau žemesnio lygio, todėl jie dar vadinami lengvaisiais siūlais. Vienoje sistemoje gali veikti kelios gijos
procesas. Čia programa veikia kaip tvarkaraščio sudarytojas ir gijų valdiklis. Taip atskiros programos atrodo atliekančios
vienu metu atlikti kelias užduotis.
Esminis skirtumas tarp gijų ir procesų yra izoliacijos lygis. Procesas turi savo rinkinį
išteklių, o gija dalijasi duomenys su kitomis gijomis. Gali atrodyti, kad tai yra klaidingas metodas, ir iš tiesų taip yra. Dėl
dabar neskirkime tam dėmesio, nes tai išeina už šio straipsnio ribų.
Procesai, gijos - gerai... Bet kas yra lygiagretumas? Sklandumas reiškia, kad vienu metu galite vykdyti kelias užduotis.
laikas. Tai nereiškia, kad tos užduotys turi būti atliekamos vienu metu - būtent tai ir yra lygiagretumas. Concurrenc in Javay taip pat neturi
reikia kelių procesorių ar net kelių branduolių. Tai galima pasiekti vieno branduolio aplinkoje naudojant
konteksto perjungimas.
Su lygiagretumu susijęs terminas yra daugiaprocesoriškumas. Tai programų savybė, leidžianti joms vienu metu vykdyti kelias užduotis. Ne kiekviena programa taiko šį metodą, tačiau tos, kurios tai daro, gali būti vadinamos daugiagrandinėmis.
Esame beveik pasiruošę, tik dar vienas apibrėžimas. Asinchronizacija reiškia, kad programa atlieka neblokines operacijas.
Jis inicijuoja užduotį ir laukdamas atsakymo atlieka kitus veiksmus. Gavęs atsakymą, jis gali reaguoti į jį.
Pagal numatytuosius nustatymus kiekvienas "Java" programa paleidžiamas vienu procesu. Tame procese yra viena gija, susijusi su pagrindinis() metodas
paraišką. Tačiau, kaip minėta, galima pasinaudoti kelių gijų mechanizmais vienoje
programa.
Sriegis yra Java klasė, kurioje įvyksta stebuklas. Tai yra anksčiau minėtos gijos objekto atvaizdavimas. Į
sukurti savo giją, galite išplėsti Sriegis klasė. Tačiau tai nerekomenduojama. Siūlai turėtų būti naudojamas kaip užduoties vykdymo mechanizmas. Užduotys yra dalys kodas kurį norime paleisti lygiagrečiuoju režimu. Juos galime apibrėžti naudodami Vykdomas sąsaja.
Bet užteks teorijos, dėliokime kodą ten, kur yra mūsų burna.
Tarkime, kad turime keletą skaičių masyvų. Norime sužinoti kiekvieno masyvo skaičių sumą. Tegul
apsimesti, kad tokių masyvų yra daug ir kiekvienas iš jų yra gana didelis. Esant tokioms sąlygoms, norime pasinaudoti vienalaikiškumu ir kiekvieną masyvą sumuoti kaip atskirą užduotį.
int[] a1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] a2 = {10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10};
int[] a3 = {3, 4, 3, 4, 4, 3, 4, 2, 1, 3, 7};
Runnable task1 = () -> {
int sum = Arrays.stream(a1).sum();
System.out.println("1. Suma yra: " + suma);
};
Runnable task2 = () -> {
int sum = Arrays.stream(a2).sum();
System.out.println("2. Suma yra: " + suma);
};
Runnable task3 = () -> {
int sum = Arrays.stream(a3).sum();
System.out.println("3. Suma yra: " + suma);
};
new Thread(task1).start();
new Thread(task2).start();
new Thread(task3).start();
Kaip matote iš pirmiau pateikto kodo Vykdomas yra funkcinė sąsaja. Joje yra vienas abstraktus metodas paleisti()
be jokių argumentų. Adresas Vykdomas sąsaja turėtų būti įgyvendinta bet kurioje klasėje, kurios egzemplioriai turi būti
vykdoma gija.
Apibrėžę užduotį, galite sukurti giją jai vykdyti. Tai galima padaryti naudojant nauja gija() konstruktorius, kuris
užima Vykdomas kaip argumentas.
Paskutinis žingsnis - paleisti() naujai sukurta gija. API taip pat yra paleisti() metodai Vykdomas irSriegis. Tačiau tai nėra būdas išnaudoti "Java" lygiagretumą. Tiesioginis kiekvieno iš šių metodų iškvietimas sukelia
užduoties vykdymas tame pačiame sraute. pagrindinis() paleidžiamas metodas.
Kai užduočių yra daug, kurti atskirą giją kiekvienai užduočiai nėra gera idėja. Sukurti Sriegis yra
sunkią operaciją, todėl daug geriau pakartotinai naudoti esamas gijas nei kurti naujas.
Kai programa sukuria daug trumpalaikių gijų, geriau naudoti gijų fondą. Srautų fondą sudaro kelios
paruoštas paleisti, bet šiuo metu neaktyvias gijas. Suteikiant a Vykdomas į fondą, vienas iš srautų iškviečiapaleisti() pateiktas metodas Vykdomas. Atlikus užduotį, gija vis dar egzistuoja ir yra neveikos būsenoje.
Gerai, supratote - pageidaujate siūlų fondo, o ne rankinio kūrimo. Bet kaip galite pasinaudoti siūlų baseinais? Svetainė Vykdytojai
klasė turi keletą statinių gamyklinių metodų, skirtų siūlų fondams kurti. Pavyzdžiui newCachedThredPool() sukuria
fondą, kuriame naujos gijos kuriamos pagal poreikį, o nenaudojamos gijos saugomos 60 sekundžių. Priešingai,newFixedThreadPool() yra fiksuotas gijų rinkinys, kuriame neveikiančios gijos laikomos neribotą laiką.
Pažiūrėkime, kaip tai galėtų veikti mūsų pavyzdyje. Dabar mums nebereikia kurti gijų rankiniu būdu. Vietoj to turime sukurtiExecutorService kuri suteikia gijų fondą. Tada galime priskirti jam užduotis. Paskutinis žingsnis - uždaryti giją
fondą, kad būtų išvengta atminties nutekėjimo. Likusi ankstesnio kodo dalis išlieka tokia pati.
ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(task1);
executor.submit(task2);
executor.submit(task3);
executor.shutdown();
Vykdomas atrodo puikus būdas kurti lygiagrečias užduotis, tačiau jis turi vieną didelį trūkumą. Jis negali grąžinti jokių
vertė. Be to, negalime nustatyti, ar užduotis baigta, ar ne. Taip pat nežinome, ar ji buvo baigta
paprastai arba išimties tvarka. Šių ligų sprendimas yra Skambinamas.
Skambinamas yra panašus į Vykdomas taip pat apima asinchronines užduotis. Pagrindinis skirtumas yra tas, kad jis gali
grąžinti reikšmę. Grąžinama vertė gali būti bet kokio (ne primityvaus) tipo, nes Skambinamas sąsaja yra parametrizuotas tipas.Skambinamas yra funkcinė sąsaja, turinti skambinti() metodą, kuris gali išmesti Išimtis.
Dabar pažiūrėkime, kaip galime panaudoti Skambinamas mūsų masyvo uždavinyje.
int[] a1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] a2 = {10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10};
int[] a3 = {3, 4, 3, 4, 4, 3, 4, 2, 1, 3, 7};
Callable task1 = () -> Arrays.stream(a1).sum();
Callable task2 = () -> Arrays.stream(a2).sum();
Callable task3 = () -> Arrays.stream(a3).sum();
ExecutorService executor = Executors.newCachedThreadPool();
Future future1 = executor.submit(task1);
Future future2 = executor.submit(task2);
Future future3 = executor.submit(task3);
System.out.println("1. Suma yra: " + future1.get());
System.out.println("2. Suma yra: " + future2.get());
System.out.println("3. Suma yra: " + future3.get());
executor.shutdown();
Gerai, matome, kaip Skambinamas sukuriamas ir pateikiamas ExecutorService. Bet kas, po velnių, yra Ateitis?Ateitis veikia kaip tiltas tarp gijų. Kiekvieno masyvo suma sukuriama atskiroje gijoje, todėl mums reikia būdo, kaip
gauti šiuos rezultatus atgal į pagrindinis().
Norėdami gauti rezultatą iš Ateitis turime skambinti gauti() metodas. Čia gali nutikti vienas iš dviejų dalykų. Pirma,
skaičiavimo, atlikto Skambinamas yra. Tuomet ją gauname iš karto. Antra, rezultatas nėra
dar nepasiruošę. Tokiu atveju gauti() metodas bus užblokuotas, kol bus gautas rezultatas.
Problema su Ateitis yra tai, kad jis veikia pagal "stūmimo paradigmą". Naudojant Ateitis turite būti kaip bosas, kuris
nuolat klausia: "Ar tavo užduotis atlikta? Ar ji paruošta?", kol bus pasiektas rezultatas. Veikti veikiant nuolatiniam spaudimui yra
brangiai kainuoja. Geriau būtų užsisakyti Ateitis ką daryti, kai jis bus pasirengęs atlikti užduotį. Deja,Ateitis negali to padaryti, bet ComputableFuture gali.
ComputableFuture veikia pagal "traukimo paradigmą". Galime nurodyti, ką daryti su rezultatu, kai jis atliks savo užduotis. Jis
yra asinchroninio metodo pavyzdys.
ComputableFuture puikiai veikia su Vykdomas bet ne su Skambinamas. Vietoj to galima pateikti užduotįComputableFuture forma Tiekėjas.
Pažiūrėkime, kaip tai susiję su mūsų problema.
int[] a1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] a2 = {10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10};
int[] a3 = {3, 4, 3, 4, 4, 3, 4, 2, 1, 3, 7};
CompletableFuture.supplyAsync(() -> Arrays.stream(a1).sum())
.thenAccept(System.out::println);
CompletableFuture.supplyAsync(() -> Arrays.stream(a2).sum())
.thenAccept(System.out::println);
CompletableFuture.supplyAsync(() -> Arrays.stream(a3).sum())
.thenAccept(System.out::println);
Pirmiausia į akis krenta tai, kad šis sprendimas yra daug trumpesnis. Be to, jis taip pat atrodo švarus ir tvarkingas.
Užduotis UžbaigiamaBūsimybė gali teikti supplyAsync() metodą, kuris priima Tiekėjas arba runAsync() kad
užima Vykdomas. Grįžtamasis ryšys - kodo dalis, kuri turėtų būti paleista užbaigus užduotį - apibrėžiamas thenAccept()
metodas.
Java pateikiama daug skirtingų požiūrių į lygiagretumą. Šiame straipsnyje vos palietėme šią temą.
Nepaisant to, apžvelgėme pagrindinius Sriegis, Vykdomas, Skambinamas, ir CallableFuture kuris kelia gerą klausimą
tolesniam temos tyrimui.
