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()
, unpaziņ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 izteiktavienība
vienības, kuras norādījusiTimeUnit
enum; piemēram,TimeUnit.SECONDS
. Šī metode metjava.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ž aNā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ā.