Programiranje

Java 101: Sočasnost Java brez bolečin, 1. del

Zaradi vse bolj zapletenih sočasnih aplikacij mnogi razvijalci ugotavljajo, da zmožnosti Java navojev na nizki ravni ne zadoščajo njihovim programskim potrebam. V tem primeru je morda čas, da odkrijete pripomočke Java Concurrency Utilities. Začnite z java.util.concurrent, s podrobnim uvodom Jeffa Friesena v ogrodje Executor, vrste sinhronizatorjev in paket Java Concurrent Collections.

Java 101: Naslednja generacija

Prvi članek v tej novi seriji JavaWorld predstavlja API za datum in čas Java.

Platforma Java zagotavlja nizko nivojske zmožnosti navojev, ki razvijalcem omogočajo pisanje sočasnih aplikacij, pri katerih se različne niti izvajajo hkrati. Standardno navajanje Java pa ima nekaj slabosti:

  • Java-ovi nizki nivoji sočasnih primitivov (sinhronizirano, hlapljivo, počakaj (), obvestiti(), in notifyAll ()) ni enostavna za pravilno uporabo. Težko je odkriti in odpraviti tudi nevarnosti navojev, kot so zastoj, stradanje niti in dirkalni pogoji, ki so posledica nepravilne uporabe primitivov.
  • Zanašanje na sinhronizirano usklajevanje dostopa med niti vodi do težav z zmogljivostjo, ki vplivajo na razširljivost aplikacij, kar je zahteva za številne sodobne aplikacije.
  • Osnovne zmožnosti navojev Java so preveč nizka stopnja. Razvijalci pogosto potrebujejo konstrukcije na višji ravni, kot so semaforji in področja niti, ki jih Java-ove zmožnosti nizkega navoja ne ponujajo. Posledično bodo razvijalci izdelali lastne konstrukcije, ki so zamudne in nagnjene k napakam.

Okvir JSR 166: Concurrency Utilities je bil zasnovan tako, da zadovolji potrebe po objektu za rezanje navojev na visoki ravni. Začet v začetku leta 2002, je bil okvir formaliziran in uveden dve leti kasneje v Javi 5. Izboljšave so sledile v Javi 6, Javi 7 in prihodnji Javi 8.

Ta dvodelni Java 101: Naslednja generacija serija predstavlja razvijalce programske opreme, ki so seznanjeni z osnovnimi nitmi Java v pakete in ogrodje Java Concurrency Utilities. V prvem delu predstavljam pregled ogrodja Java Concurrency Utilities in predstavim okvir Executor, pripomočke za sinhronizacijo in paket Java Concurrent Collections.

Razumevanje niti Java

Preden se potopite v to serijo, se prepričajte, da poznate osnove navojev. Začnite z Java 101 uvod v zmožnosti nizkega nivoja navojev Java:

  • 1. del: Predstavljanje niti in izvedljivih datotek
  • 2. del: Sinhronizacija niti
  • 3. del: Načrtovanje niti, čakanje / obveščanje in prekinitev niti
  • 4. del: Skupine niti, volatilnost, lokalne spremenljivke niti, časovniki in smrt niti

Znotraj pripomočkov Java Concurrency Utilities

Okvir Java Concurrency Utilities je knjižnica vrste ki so zasnovani za uporabo kot gradniki za ustvarjanje sočasnih razredov ali aplikacij. Te vrste so varne pred nitmi, so bile temeljito preizkušene in ponujajo visoko zmogljivost.

Vrste v pripomočkih Java Concurrency Utilities so organizirane v majhne okvire; in sicer Executor framework, sinhronizator, sočasne zbirke, ključavnice, atomske spremenljivke in Fork / Join. Nadalje so organizirani v glavni paket in par podpaketov:

  • java.util.concurrent vsebuje visokokakovostne tipe pripomočkov, ki se pogosto uporabljajo pri sočasnem programiranju. Primeri vključujejo semaforje, pregrade, področja niti in sočasne hashmape.
    • The java.util.concurrent.atomic podpaket vsebuje nizkorazredne razrede pomožnih programov, ki podpirajo programiranje za posamezne spremenljivke brez zaklepanja.
    • The java.util.concurrent.locks podpaket vsebuje nizkorazredne tipe pripomočkov za zaklepanje in čakanje na pogoje, ki se razlikujejo od uporabe nizke ravni sinhronizacije in monitorjev Java.

Okvir Java Concurrency Utilities prav tako izpostavlja nizko raven primerjaj in zamenjaj (CAS) navodila za strojno opremo, katerih različice običajno podpirajo sodobni procesorji. CAS je veliko lažji od sinhronizacijskega mehanizma na osnovi monitorja Java in se uporablja za izvajanje nekaterih zelo razširljivih sočasnih razredov. Temelji na CAS java.util.concurrent.locks.ReentrantLock razred je na primer bolj zmogljiv kot enakovreden zaslon, ki temelji na monitorju sinhronizirano primitivno. ReentrantLock ponuja več nadzora nad zaklepanjem. (V 2. delu bom razložil več o delovanju sistema CAS java.util.concurrent.)

System.nanoTime ()

Okvir Java Concurrency Utilities vključuje dolg nanoTime (), ki je član java.lang.System razred. Ta metoda omogoča dostop do časovnega vira nanosekunde zrnatosti za merjenje relativnega časa.

V naslednjih razdelkih bom predstavil tri uporabne funkcije Java Concurrency Utilities, najprej razložil, zakaj so tako pomembni za sodobno hkratnost, nato pa prikazal, kako delujejo za povečanje hitrosti, zanesljivosti, učinkovitosti in razširljivosti sočasnih aplikacij Java.

Okvir izvršitelja

Pri navoju navojev a naloga je enota dela. Ena težava pri nizki ravni navojev v Javi je ta, da je oddaja nalog tesno povezana s politiko izvrševanja nalog, kot je razvidno iz seznama 1.

Seznam 1. Server.java (različica 1)

import java.io.IOException; uvoz java.net.ServerSocket; uvoz java.net.Socket; class Server {public static void main (String [] args) vrže IOException {ServerSocket socket = new ServerSocket (9000); while (true) {final Socket s = socket.accept (); Runnable r = new Runnable () {@Override public void run () {doWork (s); }}; nova nit (r) .start (); }} statična void doWork (vtičnice) {}}

Zgornja koda opisuje preprosto strežniško aplikacijo (z doWork (vtičnica) ostane kratko za kratkost). Strežniška nit večkrat pokliče socket.accept () počakati na dohodno zahtevo in nato zažene nit za servisiranje te zahteve, ko prispe.

Ker ta aplikacija ustvari novo nit za vsako zahtevo, se ob velikem številu zahtev ne prilagodi dobro. Na primer, vsaka ustvarjena nit zahteva pomnilnik in preveč niti lahko izčrpa razpoložljivi pomnilnik, zaradi česar se aplikacija konča.

To težavo bi lahko rešili s spremembo pravilnika o izvrševanju nalog. Namesto da bi vedno ustvarili novo nit, bi lahko uporabili področje niti, v katerem bi določeno število niti servisiralo dohodne naloge. Vendar bi morali spremeniti aplikacijo, če želite to spremeniti.

java.util.concurrent vključuje okvir Izvršitelj, majhen okvir vrst, ki ločujejo oddajo nalog od politik izvrševanja nalog. Z uporabo okvira Executor je mogoče enostavno prilagoditi politiko izvrševanja nalog programa, ne da bi morali bistveno prepisovati kodo.

Znotraj okvira Executor

Izvršitveni okvir temelji na Izvršitelj vmesnik, ki opisuje izvršitelj kot kateri koli predmet, ki ga je mogoče izvršiti java.lang.Teče naloge. Ta vmesnik razglasi naslednjo samotno metodo za izvajanje a Teče naloga:

void izvedba (ukaz Runnable)

Predložite a Teče opravilo tako, da ga posredujete izvrši (izvedljivo). Če izvršitelj iz kakršnega koli razloga ne more izvesti naloge (na primer, če je bil izvajalec izklopljen), bo ta metoda vrgla RejectedExecutionException.

Ključni koncept je ta oddaja naloge je ločena od politike izvajanja nalog, ki ga opisuje Izvršitelj izvajanje. The vodljiv opravilo je tako mogoče izvršiti prek nove niti, združene niti, klicne niti itd.

Upoštevajte to Izvršitelj je zelo omejena. Na primer, izvršitelja ne morete zaustaviti ali ugotoviti, ali je asinhrono opravilo končano. Prav tako ne morete preklicati tekočega opravila. Iz teh in drugih razlogov okvir Executor ponuja vmesnik ExecutorService, ki se razširi Izvršitelj.

Pet od ExecutorServiceše posebej velja omeniti metode:

  • boolean awaitTermination (dolga časovna omejitev, enota TimeUnit) blokira klicno nit, dokler se vsa opravila ne končajo po zahtevi za zaustavitev, ne poteče časovna omejitev ali trenutna nit prekine, kar se zgodi prej. Najdaljši čas čakanja je določen z odmor, in ta vrednost je izražena v enota enote, ki jih določa TimeUnit enum; na primer, TimeUnit.SECONDS. Ta metoda vrže java.lang.InterruptedException ko je trenutna nit prekinjena. Vrne se prav ko je izvršitelj odpovedan in napačno ko poteče časovna omejitev pred zaključkom.
  • logična isShutdown () vrne prav ko je bil izvršitelj zaprt.
  • izklop void () sproži urejeno zaustavitev, v kateri se izvršijo predhodno oddane naloge, vendar nove naloge niso sprejete.
  • Prihodnja oddaja (naloga, ki jo je mogoče poklicati) predloži nalogo, ki vrne vrednost, v izvršitev in vrne a Prihodnost predstavljajo čakajoče rezultate naloge.
  • Prihodnja oddaja (izvedljiva naloga) trdi a Teče nalogo za izvedbo in vrne a Prihodnost predstavljajo to nalogo.

The Prihodnost vmesnik predstavlja rezultat asinhronega izračuna. Rezultat je znan kot prihodnosti ker običajno ne bo na voljo do nekega trenutka v prihodnosti. Lahko prikličete metode za preklic opravila, vrnete rezultat opravila (čakate neomejeno ali počaka, da poteče, ko naloga še ni končana) in ugotovite, ali je bila naloga preklicana ali končana.

The Klicljivo vmesnik je podoben Teče vmesnik, saj ponuja eno samo metodo, ki opisuje nalogo za izvedbo. Za razliko Tečeje void run () metoda, Klicljivoje V call () vrže izjemo metoda lahko vrne vrednost in vrže izjemo.

Tovarniške metode izvršiteljev

Na neki točki boste želeli dobiti izvršitelja. Okvir Executor zagotavlja Izvršitelji razred uporabnosti za ta namen. Izvršitelji ponuja več tovarniških metod za pridobivanje različnih vrst izvršiteljev, ki ponujajo posebne politike izvajanja niti. Tu so trije primeri:

  • ExecutorService newCachedThreadPool () ustvari področje niti, ki po potrebi ustvari nove niti, ki pa ponovno uporabi prej zgrajene niti, ko so na voljo. Niti, ki niso bili uporabljeni 60 sekund, se prekinejo in odstranijo iz predpomnilnika. To področje niti običajno izboljša zmogljivost programov, ki izvajajo številne kratkotrajne asinhrone naloge.
  • ExecutorService newSingleThreadExecutor () ustvari izvršitelja, ki uporablja eno samo delovno nit, ki deluje z neomejeno čakalno vrsto - naloge se dodajo v čakalno vrsto in se izvajajo zaporedno (hkrati ni aktivnih več kot ena naloga). Če se ta nit konča zaradi okvare med izvajanjem pred zaustavitvijo izvršilca, bo ustvarjena nova nit, ki bo zavzela mesto, ko bo treba izvesti nadaljnja opravila.
  • ExecutorService newFixedThreadPool (int nThreads) ustvari področje niti, ki znova uporablja določeno število niti, ki delujejo v skupni neomejeni vrsti. Kvečjemu nNitke niti aktivno obdelujejo naloge. Če so poslana dodatna opravila, ko so aktivne vse niti, počakajo v čakalni vrsti, dokler nit ni na voljo. Če se katera nit med izvajanjem pred zaustavitvijo konča z okvaro, bo ustvarjena nova nit, ki bo zavzela mesto, ko bo treba izvesti nadaljnja opravila. Niti bazena obstajajo do zaustavitve izvršilca.

Okvir Executor ponuja dodatne vrste (na primer ScheduledExecutorService vmesnik), vendar so vrste, s katerimi boste najpogosteje delali ExecutorService, Prihodnost, Klicljivo, in Izvršitelji.

Glej java.util.concurrent Javadoc za raziskovanje dodatnih vrst.

Delo z ogrodjem Executor

Ugotovili boste, da je z ogrodjem Executor precej enostavno delati. V seznamu 2 sem uporabil Izvršitelj in Izvršitelji za nadomestitev primera strežnika iz seznama 1 z bolj razširljivo alternativo, ki temelji na združenju niti.

Seznam 2. Server.java (različica 2)

import java.io.IOException; uvoz java.net.ServerSocket; uvoz java.net.Socket; uvoz java.util.concurrent.Executor; uvoz java.util.concurrent.Executors; class Server {static Executor pool = Executors.newFixedThreadPool (5); public static void main (String [] args) vrže IOException {ServerSocket socket = new ServerSocket (9000); while (true) {final Socket s = socket.accept (); Runnable r = new Runnable () {@Override public void run () {doWork (s); }}; pool.execute (r); }} statična void doWork (vtičnice) {}}

Seznam 2 uporablja newFixedThreadPool (int) pridobiti izvršitelja, ki temelji na področju niti, ki ponovno uporabi pet niti. Prav tako nadomešča nova nit (r) .start (); s pool.execute (r); za izvajanje nalog, ki jih je mogoče izvajati, prek katere koli od teh niti.

Seznam 3 predstavlja še en primer, v katerem aplikacija bere vsebino poljubne spletne strani. Prikaže nastale vrstice ali sporočilo o napaki, če vsebina ni na voljo v največ petih sekundah.

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