Programmēšana

Java 101: Java vienlaicīgums bez sāpēm, 1. daļa

Palielinoties vienlaicīgo lietojumprogrammu sarežģītībai, daudzi izstrādātāji atklāj, ka Java zemā līmeņa pavedienu iespējas nav pietiekamas viņu programmēšanas vajadzībām. Tādā gadījumā varētu būt laiks atklāt Java Concurrency Utilities. Sāciet ar java.util.concurrent, ar Jeff Friesen detalizētu ievadu Executor sistēmā, sinhronizatoru veidiem un Java Concurrent Collections paketi.

Java 101: nākamā paaudze

Pirmais šīs jaunās JavaWorld sērijas raksts iepazīstina ar Java datuma un laika API.

Java platforma nodrošina zema līmeņa pavedienu iespējas, kas ļauj izstrādātājiem rakstīt vienlaicīgas lietojumprogrammas, kurās vienlaikus tiek izpildīti dažādi pavedieni. Java standarta vītņošanai tomēr ir dažas ēnas puses:

  • Java zemā līmeņa vienlaicīguma primitīvi (sinhronizēts, gaistošs, pagaidiet (), paziņot(), un paziņot visiem ()) nav viegli pareizi lietot. Arī nepareizas primitīvu lietošanas rezultātā radušos vītņošanas riskus, piemēram, strupceļu, diegu badu un sacensību apstākļus, ir grūti atklāt un atkļūdot.
  • Paļaujoties uz sinhronizēts lai koordinētu piekļuvi starp pavedieniem, rodas veiktspējas problēmas, kas ietekmē lietojumprogrammu mērogojamību, kas ir prasība daudzām mūsdienu lietojumprogrammām.
  • Java galvenās vītņošanas iespējas ir arī zems līmenis. Izstrādātājiem bieži nepieciešamas augstāka līmeņa konstrukcijas, piemēram, semaforas un pavedienu kopas, kuras Java zemā līmeņa pavedienu iespējas nepiedāvā. Rezultātā izstrādātāji izveidos paši savus konstrukcijas, kas ir laikietilpīga un pakļauta kļūdām.

JSR 166: Vienlaicīguma komunālo pakalpojumu ietvars tika izstrādāts, lai apmierinātu vajadzību pēc augsta līmeņa vītņošanas iekārtas. Uzsākta 2002. gada sākumā, sistēma tika formalizēta un ieviesta divus gadus vēlāk Java 5. Uzlabojumi ir sekojuši Java 6, Java 7 un gaidāmajai Java 8.

Šī divdaļīgā Java 101: nākamā paaudze sērija iepazīstina programmatūras izstrādātājus, kuri pārzina Java pamata pavedienus, ar Java Concurrency Utilities pakotnēm un sistēmu. 1. daļā es sniedzu pārskatu par Java Concurrency Utilities ietvaru un iepazīstinu ar tā izpildītāju ietvaru, sinhronizatora utilītprogrammām un Java Concurrent Collections paketi.

Izpratne par Java pavedieniem

Pirms ienirstat šajā sērijā, pārliecinieties, ka esat iepazinies ar vītnes pamatiem. Sāciet ar Java 101 ievads Java zemā līmeņa vītņošanas iespējām:

  • 1. daļa: Vītņu un skrējienu iepazīstināšana
  • 2. daļa: Vītnes sinhronizācija
  • 3. daļa: Vītņu plānošana, gaidīšana / paziņošana un pavedienu pārtraukšana
  • 4. daļa: pavedienu grupas, svārstīgums, lokālie pavedienu mainīgie, taimeri un pavedienu bojāeja

Java vienlaicīguma utilītu iekšpusē

Java Concurrency Utilities ietvars ir bibliotēka veidi kas ir paredzēti izmantošanai kā celtniecības bloki vienlaicīgu klašu vai lietojumprogrammu veidošanai. Šie veidi ir droši ar vītnēm, ir rūpīgi pārbaudīti un piedāvā augstu veiktspēju.

Java vienlaicīguma utilītu veidi ir sakārtoti mazos ietvaros; Proti, izpildītāja ietvars, sinhronizators, vienlaicīgas kolekcijas, slēdzenes, atomu mainīgie un Fork / Join. Tie ir tālāk sakārtoti galvenajā paketē un pāris paketēs:

  • java.util.concurrent satur augsta līmeņa utilītu veidus, kurus parasti izmanto vienlaicīgā programmēšanā. Piemēri ietver semaforas, barjeras, pavedienu kopas un vienlaicīgas hashmaps.
    • The java.util.concurrent.atomic apakškopā ir zema līmeņa komunālo pakalpojumu klases, kas atbalsta atsevišķu mainīgo programmēšanu bez bloķēšanas ar vītnēm.
    • The java.util.concurrent.locks apakškopā ir zema līmeņa utilītu veidi bloķēšanai un apstākļu gaidīšanai, kas atšķiras no Java zemā līmeņa sinhronizācijas un monitoru izmantošanas.

Java Concurrency Utilities ietvars arī atklāj zemo līmeni salīdzināt un nomainīt (CAS) aparatūras instrukcija, kuras variantus parasti atbalsta mūsdienu procesori. CAS ir daudz vieglāks nekā Java monitora bāzes sinhronizācijas mehānisms, un to izmanto, lai ieviestu dažas ļoti mērogojamas vienlaicīgas klases. CAS bāzes java.util.concurrent.locks.ReentrantLock klase, piemēram, ir veiktspējīgāka nekā līdzvērtīga monitora bāze sinhronizēts primitīvs. ReentrantLock piedāvā lielāku kontroli pār bloķēšanu. (2. daļā es vairāk paskaidrošu, kā darbojas CAS java.util.concurrent.)

System.nanoTime ()

Java Concurrency Utilities ietvars ietver garš nanoTime (), kas ir java.lang.Sistēma klasē. Šī metode ļauj piekļūt nanosekunžu granulitātes laika avotam relatīvo laika mērījumu veikšanai.

Nākamajās sadaļās es iepazīstināšu ar trim noderīgām Java Concurrency Utilities funkcijām, vispirms izskaidrojot, kāpēc tās ir tik svarīgas mūsdienu vienlaicīgumam, un pēc tam parādot, kā tās darbojas, lai palielinātu vienlaicīgu Java lietojumprogrammu ātrumu, uzticamību, efektivitāti un mērogojamību.

Izpildītāja ietvars

Vītnē a uzdevums ir darba vienība. Viena no Java zema līmeņa pavedienu problēmām ir tā, ka uzdevumu iesniegšana ir cieši saistīta ar uzdevumu izpildes politiku, kā parādīts 1. sarakstā.

Listing 1. Server.java (versija 1)

importēt java.io.IOException; importēt java.net.ServerSocket; importēt java.net.Socket; klases serveris {public static void main (virkne [] argumenti) izmet IOException {ServerSocket ligzda = new ServerSocket (9000); while (patiess) {final Socket s = socket.accept (); Runnable r = new Runnable () {@ Pārvarēt public void run () {doWork (s); }}; jauns pavediens (r) .start (); }} static void doWork (Socket s) {}}

Iepriekš minētais kods apraksta vienkāršu servera lietojumprogrammu (ar doWork (ligzda) atstāj tukšu īsuma dēļ). Servera pavediens atkārtoti zvana socket.accept () gaidīt ienākošo pieprasījumu un pēc tam uzsāk pavedienu šī pieprasījuma apkalpošanai, kad tas pienāk.

Tā kā šī lietojumprogramma katram pieprasījumam izveido jaunu pavedienu, tā netiek labi mērogota, saskaroties ar milzīgu pieprasījumu skaitu. Piemēram, katram izveidotajam pavedienam ir nepieciešama atmiņa, un pārāk daudz pavedienu var iztukšot pieejamo atmiņu, liekot lietojumprogrammai pārtraukt darbību.

Jūs varētu atrisināt šo problēmu, mainot uzdevuma izpildes politiku. Tā vietā, lai vienmēr izveidotu jaunu pavedienu, jūs varētu izmantot pavedienu kopu, kurā fiksēts pavedienu skaits apkalpotu ienākošos uzdevumus. Lai veiktu šīs izmaiņas, jums tomēr būs jāpārraksta lietojumprogramma.

java.util.concurrent ietver izpildītāja ietvaru, nelielu veidu sistēmu, kas atdala uzdevumu iesniegšanu no uzdevuma izpildes politikām. Izmantojot ietvaru Executor, ir iespējams viegli noregulēt programmas uzdevumu izpildes politiku, būtiski nepārrakstot kodu.

Izpildītāja ietvara iekšpusē

Izpildītāja ietvara pamatā ir Izpildītājs interfeiss, kas apraksta izpildītājs kā jebkurš objekts, kas spēj izpildīt java.lang. Skrienams uzdevumi. Šī saskarne deklarē šādu vienīgo metodi a Skrienams uzdevums:

void execute (Runnable komanda)

Jūs iesniedzat a Skrienams uzdevumu, nododot to izpildīt (Runnable). Ja izpildītājs kāda iemesla dēļ nevar izpildīt uzdevumu (piemēram, ja izpildītājs ir izslēgts), šī metode RejectedExecutionException.

Galvenais jēdziens ir tāds uzdevuma iesniegšana ir atdalīta no uzdevuma izpildes politikas, kuru apraksta an Izpildītājs ieviešana. The skrienams Tādējādi uzdevumu var izpildīt, izmantojot jaunu pavedienu, apvienotu pavedienu, izsaucošo pavedienu utt.

Pieraksti to Izpildītājs ir ļoti ierobežots. Piemēram, jūs nevarat izslēgt izpildītāju vai noteikt, vai asinhronais uzdevums ir pabeigts. Jūs nevarat arī atcelt skriešanas uzdevumu. Šo un citu iemeslu dēļ izpilddirektors nodrošina ExecutorService saskarni, kas paplašinās Izpildītājs.

Pieci no ExecutorServiceīpaši ievērības cienīgas ir metodes:

  • boolean awaitTermination (ilgs noildze, TimeUnit vienība) bloķē izsaucošo pavedienu, līdz visi uzdevumi ir pabeigti pēc izslēgšanas pieprasījuma, iestājas taimauts vai tiek pārtraukta pašreizējā pavediens, atkarībā no tā, kas notiek vispirms. Maksimālo gaidīšanas laiku nosaka pārtraukums, un šī vērtība ir izteikta vienība vienības, kuras norādījusi TimeUnit enum; piemēram, TimeUnit.SECONDS. Šī metode met java.lang.InterruptedException kad pašreizējais pavediens tiek pārtraukts. Tas atgriežas taisnība kad izpildītājs tiek izbeigts un nepatiesa kad noildze beidzas pirms pārtraukšanas.
  • boolean isShutdown () atgriežas taisnība kad izpildītājs ir slēgts.
  • anulēta izslēgšana () uzsāk kārtīgu izslēgšanu, kurā tiek izpildīti iepriekš iesniegtie uzdevumi, bet netiek pieņemti jauni uzdevumi.
  • Turpmākā iesniegšana (izsaucamais uzdevums) iesniedz izpildei vērtību atgriežošu uzdevumu un atgriež a Nākotne pārstāvot gaidāmos uzdevuma rezultātus.
  • Nākotnes iesniegšana (izpildāms uzdevums) iesniedz a Skrienams uzdevumu izpildei un atgriež a Nākotne pārstāvot šo uzdevumu.

The Nākotne interfeiss atspoguļo asinhronās skaitļošanas rezultātu. Rezultāts ir pazīstams kā a nākotnē jo parasti tas nebūs pieejams tikai kādu brīdi nākotnē. Varat izmantot metodes, lai atceltu uzdevumu, atgrieztu uzdevuma rezultātu (gaidot uz nenoteiktu laiku vai noildzes laiku, kad uzdevums nav pabeigts) un lai noteiktu, vai uzdevums ir atcelts vai ir pabeigts.

The Zvanāms saskarne ir līdzīga Skrienams saskarni, jo tā nodrošina vienu metodi, kas apraksta izpildāmo uzdevumu. Atšķirībā no Skrienams's anulēt palaist () metode, Zvanāms's V zvans () izmet izņēmumu metode var atgriezt vērtību un mest izņēmumu.

Izpildītāja rūpnīcas metodes

Kādā brīdī jūs vēlaties iegūt izpildītāju. Izpildītāja ietvars piegādā Izpildītāji lietderības klase šim nolūkam. Izpildītāji piedāvā vairākas rūpnīcas metodes dažādu veidu izpildītāju iegūšanai, kas piedāvā īpašas pavedienu izpildes politikas. Šeit ir trīs piemēri:

  • ExecutorService newCachedThreadPool () izveido pavedienu kopu, kas pēc vajadzības izveido jaunus pavedienus, bet atkārtoti izmanto iepriekš izveidotos pavedienus, kad tie ir pieejami. Tēmas, kas nav izmantotas 60 sekundes, tiek pārtrauktas un noņemtas no kešatmiņas. Šis pavedienu kopums parasti uzlabo to programmu darbību, kuras izpilda daudz īslaicīgu asinhronu uzdevumu.
  • ExecutorService newSingleThreadExecutor () izveido izpildītāju, kas izmanto vienu darba ņēmēja pavedienu, kas darbojas bez neierobežotas rindas - uzdevumi tiek pievienoti rindai un izpildīti secīgi (vienlaikus ir aktīvs ne vairāk kā viens uzdevums). Ja šī pavediena darbība tiek pārtraukta kļūmes laikā pirms izpildītāja izslēgšanas, tiks izveidots jauns pavediens, kas ieņems vietu, kad būs jāveic nākamie uzdevumi.
  • ExecutorService newFixedThreadPool (int nThreads) izveido pavedienu kopu, kas atkārtoti izmanto noteiktu skaitu pavedienu, kas darbojas ārpus kopīgas neierobežotas rindas. Maksimāli nThreads pavedieni aktīvi apstrādā uzdevumus. Ja papildu uzdevumi tiek iesniegti, kad visi pavedieni ir aktīvi, tie gaida rindā, līdz pavediens būs pieejams. Ja kāds pavediens tiek pārtraukts kļūmes izpildes laikā pirms izslēgšanas, tiks izveidots jauns pavediens, kas ieņems vietu, kad būs jāveic nākamie uzdevumi. Baseina pavedieni pastāv līdz izpildītāja izslēgšanai.

Izpildītāja ietvars piedāvā papildu veidus (piemēram, ScheduledExecutorService interfeiss), bet veidi, ar kuriem jūs, visticamāk, strādājat ExecutorService, Nākotne, Zvanāms, un Izpildītāji.

Skatīt java.util.concurrent Javadoc, lai izpētītu papildu veidus.

Darbs ar izpildītāja sistēmu

Jūs atradīsit, ka izpildītāja ietvars ir diezgan viegli strādājams. 2. sarakstā es esmu izmantojis Izpildītājs un Izpildītāji lai aizstātu servera piemēru no 1. saraksta ar mērogojamāku pavedienu kopu balstītu alternatīvu.

Listing 2. Server.java (2. versija)

importēt java.io.IOException; importēt java.net.ServerSocket; importēt java.net.Socket; importēt java.util.concurrent.Executor; importēt java.util.concurrent.Executors; klases serveris {static Executor pool = Executors.newFixedThreadPool (5); public static void main (String [] args) izmet IOException {ServerSocket socket = new ServerSocket (9000); while (taisnība) {final Socket s = socket.accept (); Runnable r = new Runnable () {@ Pārvarēt public void run () {doWork (s); }}; baseins.izpildīt (r); }} static void doWork (Socket s) {}}

Uzskaitot 2 lietojumus newFixedThreadPool (int) lai iegūtu pavedienu kopas izpildītāju, kas atkārtoti izmanto piecus pavedienus. Tas arī aizstāj jauns pavediens (r) .start (); ar baseins.izpildīt (r); izpildāmu uzdevumu izpildei, izmantojot jebkuru no šiem pavedieniem.

3. sarakstā ir vēl viens piemērs, kurā lietojumprogramma nolasa patvaļīgas tīmekļa lapas saturu. Tas izsūta iegūtās rindas vai kļūdas ziņojumu, ja saturs nav pieejams ne vairāk kā piecu sekunžu laikā.

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