9 kļūdas, no kurām jāizvairās, programmējot Java valodā
No kādām kļūdām vajadzētu izvairīties, programmējot Java valodā? Šajā rakstā mēs atbildēsim uz šo jautājumu.
Izlasiet mūsu emuāru sērijas pirmo daļu, kas veltīta vienlaicīgai darbībai programmā Java. Turpmākajā rakstā mēs sīkāk aplūkosim atšķirības starp pavedieniem un procesiem, pavedienu baseiniem, izpildītājiem un daudz ko citu!
Kopumā tradicionālā programmēšanas pieeja ir secīga. Programmā viss notiek soli pa solim.
Taču patiesībā paralēli darbojas visa pasaule - tā ir spēja vienlaicīgi izpildīt vairāk nekā vienu uzdevumu.
Apspriest tādas progresīvas tēmas kā. vienlaicīgums Java vai daudzpavedienu režīmā, mums ir jāvienojas par kopīgām definīcijām, lai pārliecinātos, ka esam vienisprātis.
Sāksim ar pamatiem. Nesekvenciālajā pasaulē mums ir divu veidu vienlaicības reprezentanti: procesi un
pavedieni. Process ir izpildītas programmas gadījums. Parasti tas ir izolēts no citiem procesiem.
Operētājsistēma ir atbildīga par resursu piešķiršanu katram procesam. Turklāt tā darbojas kā diriģents, kas
plāno un kontrolē to izpildi.
Diegs ir sava veida process, bet zemākā līmenī, tāpēc to sauc arī par vieglo diegu. Vairāki pavedieni var darboties vienā
process. Šajā gadījumā programma darbojas kā plānošanas programma un pavedienu kontrolieris. Šādā veidā atsevišķas programmas, šķiet, veic
vairākus uzdevumus vienlaicīgi.
Pamatatšķirība starp pavedieniem un procesiem ir izolācijas līmenis. Procesam ir savs
resursus, savukārt pavediens koplieto dati ar citiem pavedieniem. Tā var šķist uz kļūdām tendēta pieeja, un tā patiešām ir. Attiecībā uz
tagad neiedziļināsimies šajā jautājumā, jo tas ir ārpus šī raksta darbības jomas.
Procesi, pavedieni - labi... Bet kas īsti ir vienlaicīgums? Vienlaicīga izpilde nozīmē, ka vienlaicīgi var izpildīt vairākus uzdevumus.
laiks. Tas nenozīmē, ka šiem uzdevumiem ir jāstrādā vienlaicīgi - tas ir paralēlisms. Concurrenc in Javay arī nav
ir nepieciešami vairāki procesori vai pat vairāki kodoli. To var panākt viena kodola vidē, izmantojot
konteksta pārslēgšana.
Ar vienlaicīgumu saistīts termins ir daudzpavedienu lietojums. Tā ir programmu iezīme, kas ļauj tām izpildīt vairākus uzdevumus vienlaicīgi. Ne visas programmas izmanto šo pieeju, bet tās, kas to dara, var saukt par daudzpavedienu programmām.
Mēs esam gandrīz gatavi doties ceļā, tikai vēl viena definīcija. Asinhronitāte nozīmē, ka programma veic nebloķējošas operācijas.
Tā uzsāk uzdevumu un pēc tam, gaidot atbildi, turpina veikt citas darbības. Kad tā saņem atbildi, tā var reaģēt uz to.
Pēc noklusējuma katrs Java lietojumprogramma darbojas vienā procesā. Šajā procesā ir viens pavediens, kas saistīts ar galvenais() metode
pieteikumu. Tomēr, kā jau minēts, ir iespējams izmantot vairāku pavedienu mehānismus vienā sistēmā.
programma.
Vītne ir Java klase, kurā notiek burvju darbība. Tas ir iepriekš minētā pavediena objekta attēlojums. Uz
izveidot savu pavedienu, varat paplašināt Vītne klase. Tomēr tā nav ieteicama pieeja. Diegi jāizmanto kā mehānisms, kas palaist uzdevumu. Uzdevumi ir kods ko vēlamies palaist vienlaicīgā režīmā. Tos varam definēt, izmantojot Runnable saskarne.
Bet pietiek ar teoriju, izmantosim kodu tur, kur ir mūsu mute.
Pieņemsim, ka mums ir vairāki skaitļu masīvi. Katram masīvam mēs vēlamies uzzināt masīvā esošo skaitļu summu. Pieņemsim
izliekas, ka šādu masīvu ir daudz, un katrs no tiem ir salīdzinoši liels. Šādos apstākļos mēs vēlamies izmantot vienlaicīgumu un summēt katru masīvu kā atsevišķu uzdevumu.
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. Summa ir: " + sum);
};
Runnable task2 = () -> {
int sum = Arrays.stream(a2).sum();
System.out.println("2. Summa ir: " + sum);
};
Runnable uzdevums3 = () -> {
int sum = Arrays.stream(a3).sum();
System.out.println("3. Summa ir: " + sum);
};
new Thread(task1).start();
new Thread(task2).start();
new Thread(task3).start();
Kā redzams iepriekš pievienotajā kodā. Runnable ir funkcionāla saskarne. Tā satur vienu abstraktu metodi palaist()
bez argumentiem. Portāls Runnable interfeiss jāimplementē jebkurai klasei, kuras eksemplāri ir paredzēti, lai būtu
izpilda pavediens.
Kad uzdevums ir definēts, varat izveidot pavedienu tā izpildei. To var izdarīt, izmantojot jauns pavediens() konstruktors, kas
aizņem Runnable kā argumentu.
Pēdējais solis ir sākt() jaunizveidots pavediens. API ir arī palaist() metodes Runnable unVītne. Tomēr tas nav veids, kā izmantot vienlaicīgumu Java. Katras no šīm metodēm tiešais izsaukums rada
izpildīt uzdevumu tajā pašā pavedienā, kurā galvenais() metode darbojas.
Ja ir daudz uzdevumu, nav laba ideja katram uzdevumam izveidot atsevišķu pavedienu. Izveidojot Vītne ir
smagsvara operācija, un ir daudz labāk atkārtoti izmantot esošos pavedienus, nevis radīt jaunus.
Ja programma rada daudz īslaicīgu pavedienu, labāk ir izmantot pavedienu pūlu. Vītņu fonds satur vairākus pavedienus.
gatavi darbam, bet pašlaik neaktīvi pavedieni. Sniedzot Runnable uz pūlu izraisa vienu no pavedieniem, kas izsaucpalaist() dotā metode Runnable. Pēc uzdevuma pabeigšanas pavediens joprojām pastāv un atrodas dīkstāves stāvoklī.
Labi, jūs saprotat - dodiet priekšroku pavedienu pūlam, nevis manuālai izveidei. Bet kā jūs varat izmantot pavedienu pūlus? Izmantojot Izpildītāji
klasē ir vairākas statiskas rūpnīcas metodes pavedienu pūlu konstruēšanai. Piemēram newCachedThredPool() izveido
pūlu, kurā pēc vajadzības tiek izveidoti jauni pavedieni un 60 sekundes tiek saglabāti dīkstāves pavedieni. Turpretī,newFixedThreadPool() satur fiksētu pavedienu kopu, kurā bezdarbības pavedieni tiek glabāti neierobežotu laiku.
Apskatīsim, kā tas varētu darboties mūsu piemērā. Tagad pavedieni nav jāveido manuāli. Tā vietā mums ir jāizveidoExecutorService kas nodrošina pavedienu kopfondu. Tad mēs varam tam piešķirt uzdevumus. Pēdējais solis ir slēgt pavedienu
pool, lai izvairītos no atmiņas noplūdes. Pārējais iepriekšējais kods paliek nemainīgs.
ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(task1);
executor.submit(uzdevums2);
executor.submit(uzdevums3);
executor.shutdown();
Runnable šķiet noderīgs veids, kā izveidot vienlaicīgus uzdevumus, taču tam ir viens būtisks trūkums. Tas nevar atdot nevienu
vērtība. Turklāt mēs nevaram noteikt, vai uzdevums ir vai nav pabeigts. Mēs arī nezinām, vai tas ir pabeigts.
parasti vai izņēmuma kārtā. Risinājums šīm slimībām ir Izsaucams.
Izsaucams ir līdzīgs Runnable tā ietver arī asinhronus uzdevumus. Galvenā atšķirība ir tā, ka tā spēj
atgriezt vērtību. Atgrieztā vērtība var būt jebkura (ne primitīvā) tipa, jo Izsaucams saskarne ir parametrizēts tips.Izsaucams ir funkcionāla saskarne, kurai ir izsaukt() metode, kas var mest Izņēmums.
Tagad aplūkosim, kā mēs varam izmantot Izsaucams mūsu masīva problēmā.
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 uzdevums1 = () -> Arrays.stream(a1).sum();
Callable task2 = () -> Arrays.stream(a2).sum();
Callable uzdevums3 = () -> 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. Summa ir: " + future1.get());
System.out.println("2. Summa ir: " + future2.get());
System.out.println("3. Summa ir: " + future3.get());
executor.shutdown();
Labi, mēs varam redzēt, kā Izsaucams tiek izveidots un pēc tam iesniegts ExecutorService. Bet kas, pie velna, ir Nākotne?Nākotne darbojas kā tilts starp pavedieniem. Katra masīva summa tiek veidota atsevišķā pavedienā, un mums ir nepieciešams veids, kā
saņemt šos rezultātus atpakaļ uz galvenais().
Lai iegūtu rezultātu no Nākotne mums jāzvana iegūt() metode. Šeit var notikt viena no divām lietām. Pirmkārt,
aprēķina rezultāts, ko veic Izsaucams ir pieejams. Tad mēs to saņemam nekavējoties. Otrkārt, rezultāts nav
vēl nav gatavs. Tādā gadījumā iegūt() metode tiks bloķēta, līdz būs pieejams rezultāts.
Jautājums par Nākotne ir tas, ka tā darbojas saskaņā ar "push paradigmu". Izmantojot Nākotne jums ir jābūt kā priekšniekam, kurš
pastāvīgi jautā: "Vai tavs uzdevums ir izpildīts? Vai tas ir gatavs?", līdz tas sniedz rezultātu. Darbība pastāvīga spiediena apstākļos ir
dārgi. Daudz labāka pieeja būtu pasūtīt Nākotne ko darīt, kad tas ir gatavs savam uzdevumam. Diemžēl,Nākotne to nevar izdarīt, bet ComputableFuture var.
ComputableFuture darbojas "pull paradigmā". Kad tas ir izpildījis savus uzdevumus, mēs varam tam norādīt, ko darīt ar rezultātu. Tas
ir asinhronas pieejas piemērs.
ComputableFuture lieliski darbojas ar Runnable bet ne ar Izsaucams. Tā vietā ir iespējams sniegt uzdevumu, laiComputableFuture veidā Piegādātājs.
Aplūkosim, kā iepriekš minētais attiecas uz mūsu problēmu.
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);
Pirmais, kas jums pārsteidz, ir tas, cik daudz īsāks ir šis risinājums. Turklāt tas arī izskatās glīti un kārtīgi.
Uzdevums PabeidzamsBūtība var nodrošināt supplyAsync() metode, kas izmanto Piegādātājs vai RunAsync() ka
aizņem Runnable. Atgriezenisko zvanu - koda fragmentu, kas jāpalaiž pēc uzdevuma pabeigšanas - definē ar thenAccept()
metode.
Java piedāvā daudz dažādu pieejas vienlaicīgai darbībai. Šajā rakstā mēs tikai nedaudz pieskārāmies šai tēmai.
Tomēr mēs aptvērām pamatus par Vītne, Runnable, Izsaucams, un CallableFuture kas izvirza labu punktu
tālākai tēmas izpētei.
