Med den økende kompleksiteten av samtidige applikasjoner, finner mange utviklere at Java's lavt nivå-trådingsfunksjoner ikke er tilstrekkelig for deres programmeringsbehov. I så fall kan det være på tide å oppdage Java Concurrency Utilities. Kom i gang med java.util.concurrent
, med Jeff Friesens detaljerte introduksjon til Executor-rammeverket, synkroniseringstyper og Java Concurrent Collections-pakken.
Java 101: Neste generasjon
Den første artikkelen i denne nye JavaWorld-serien introduserer Java Date and Time API.
Java-plattformen gir funksjoner på lavt nivå threading som gjør det mulig for utviklere å skrive samtidige applikasjoner der forskjellige tråder kjøres samtidig. Standard Java-threading har imidlertid noen ulemper:
- Java's lavnivå samtidige primitiver (
synkronisert
,flyktige
,vente()
,gi beskjed()
, ognotifyAll ()
) er ikke enkle å bruke riktig. Det er også vanskelig å oppdage og feilsøke tråderisiko som dødvann, sult på tråden og raseforhold, som skyldes feil bruk av primitiver. - Avhenger av
synkronisert
å koordinere tilgang mellom tråder fører til ytelsesproblemer som påvirker applikasjonsskalerbarhet, et krav for mange moderne applikasjoner. - Java grunnleggende threading evner er også lavt nivå. Utviklere trenger ofte konstruksjoner på høyere nivå som semaforer og trådbassenger, noe som Java ikke har med lavt nivå. Som et resultat vil utviklere bygge sine egne konstruksjoner, noe som er både tidkrevende og feilutsatt.
JSR 166: Concurrency Utilities framework ble designet for å dekke behovet for et gjengeanlegg på høyt nivå. Initiativet tidlig i 2002 ble rammeverket formalisert og implementert to år senere i Java 5. Forbedringer har fulgt i Java 6, Java 7 og den kommende Java 8.
Denne todelte Java 101: Neste generasjon serien introduserer programvareutviklere som er kjent med grunnleggende Java-threading til Java Concurrency Utilities-pakker og rammeverk. I del 1 presenterer jeg en oversikt over rammeverket Java Concurrency Utilities og introduserer dets Executor-rammeverk, synkroniseringsverktøy og Java Concurrent Collections-pakken.
Forstå Java-tråder
Før du dykker inn i denne serien, må du sørge for at du er kjent med det grunnleggende om å tråde. Start med Java 101 introduksjon til Java's lavt nivå threading evner:
- Del 1: Introduksjon av tråder og løpere
- Del 2: Trådesynkronisering
- Del 3: Trådplanlegging, vent / varsling og trådavbrudd
- Del 4: Trådgrupper, volatilitet, tråd-lokale variabler, tidtakere og tråddød
Inne i Java Concurrency Utilities
Java Concurrency Utilities framework er et bibliotek med typer som er designet for å brukes som byggesteiner for å lage samtidige klasser eller applikasjoner. Disse typene er trådsikre, har blitt grundig testet og gir høy ytelse.
Typer i Java Concurrency Utilities er organisert i små rammer; nemlig Executor framework, synchronizer, samtidige samlinger, låser, atomvariabler og Fork / Join. De er videre organisert i en hovedpakke og et par underpakker:
- java.util.concurrent inneholder verktøytyper på høyt nivå som ofte brukes i samtidig programmering. Eksempler inkluderer semaforer, barrierer, trådbassenger og samtidige hashmaps.
- De java.util.concurrent.atomic underpakken inneholder verktøy på lavt nivå som støtter låsfri trådsikker programmering på enkeltvariabler.
- De java.util.concurrent.locks underpakken inneholder verktøynivåer på lavt nivå for å låse og vente på forhold, som er forskjellige fra Java-synkronisering og monitorer på lavt nivå.
Java Concurrency Utilities-rammeverket utsetter også lavt nivå sammenlign og bytt (CAS) maskinvareinstruksjon, som varianter ofte støttes av moderne prosessorer. CAS er mye lettere enn Javas skjermbaserte synkroniseringsmekanisme og brukes til å implementere noen svært skalerbare samtidige klasser. CAS-baserte java.util.concurrent.locks.ReentrantLock
klasse, for eksempel, er mer performant enn den tilsvarende skjermbaserte synkronisert
primitiv. ReentrantLock
gir mer kontroll over låsing. (I del 2 forklarer jeg mer om hvordan CAS fungerer i java.util.concurrent
.)
System.nanoTime ()
Java Concurrency Utilities-rammeverket inkluderer lang nanoTime ()
, som er medlem av java.lang.System
klasse. Denne metoden gir tilgang til en tidskilde for nanosekund-granularitet for å foreta relative tidsmålinger.
I de neste avsnittene vil jeg introdusere tre nyttige funksjoner i Java Concurrency Utilities, først forklare hvorfor de er så viktige for moderne samtidighet, og deretter demonstrere hvordan de jobber for å øke hastigheten, påliteligheten, effektiviteten og skalerbarheten til samtidige Java-applikasjoner.
The Executor framework
I tråder, a oppgave er en arbeidsenhet. Et problem med lavt nivåtråding i Java er at innlevering av oppgaver er tett kombinert med en policy for oppgaveutførelse, som vist i Listing 1.
Oppføring 1. Server.java (Versjon 1)
importere java.io.IOException; importere java.net.ServerSocket; importere java.net.Socket; klasse Server {public static void main (String [] args) kaster IOException {ServerSocket socket = ny ServerSocket (9000); while (true) {final Socket s = socket.accept (); Runnable r = new Runnable () {@Override public void run () {doWork (s); }}; ny tråd (r) .start (); }} statisk ugyldig doWork (stikkontakter) {}}
Ovennevnte kode beskriver en enkel serverapplikasjon (med doWork (stikkontakt)
igjen tom for kortfattethet). Servertråden ringer gjentatte ganger socket.accept ()
å vente på en innkommende forespørsel, og starter deretter en tråd for å betjene denne forespørselen når den ankommer.
Fordi denne applikasjonen oppretter en ny tråd for hver forespørsel, skalerer den seg ikke bra når den møter et stort antall forespørsler. For eksempel krever hver opprettet tråd minne, og for mange tråder kan tømme det tilgjengelige minnet, noe som tvinger applikasjonen til å avsluttes.
Du kan løse dette problemet ved å endre policyen for utførelse av oppgaver. I stedet for å alltid lage en ny tråd, kan du bruke en trådgruppe, der et fast antall tråder vil betjene innkommende oppgaver. Du må imidlertid omskrive applikasjonen for å gjøre denne endringen.
java.util.concurrent
inkluderer Executor-rammeverket, et lite rammeverk av typer som frakopler innsending av oppgaver fra oppgaveutførelsespolitikk. Ved hjelp av Executor-rammeverket er det mulig å enkelt justere et programs policy for oppgaveutførelse uten å måtte omskrive koden din betydelig.
Inne i Executor-rammen
Utøvende rammeverk er basert på Leder
grensesnitt, som beskriver en utfører som ethvert objekt som er i stand til å utføres java.lang.Runnable
oppgaver. Dette grensesnittet erklærer følgende ensomme metode for å utføre en Kjørbar
oppgave:
void execute (Runnable command)
Du sender inn en Kjørbar
oppgave ved å overføre den til kjør (Runnable)
. Hvis eksekutøren ikke kan utføre oppgaven av en eller annen grunn (for eksempel hvis eksekutoren er stengt), vil denne metoden kaste et AvvistExecutionException
.
Nøkkelkonseptet er det oppgaveinnlevering er frikoblet fra oppgaveutførelsespolitikken, som er beskrevet av en Leder
gjennomføring. De kjørbar oppgaven er dermed i stand til å utføre via en ny tråd, en samlet tråd, ringetråden og så videre.
Noter det Leder
er veldig begrenset. Du kan for eksempel ikke slå av en utførende eller avgjøre om en asynkron oppgave er fullført. Du kan heller ikke avbryte en løpende oppgave. Av disse og andre grunner gir Executor-rammeverket et ExecutorService-grensesnitt som utvides Leder
.
Fem av ExecutorService
metodene er spesielt bemerkelsesverdige:
- boolean awaitTermination (lang tidsavbrudd, TimeUnit-enhet) blokkerer anropstråden til alle oppgaver er fullført etter en avslutningsforespørsel, tidsavbruddet oppstår, eller den gjeldende tråden blir avbrutt, avhengig av hva som skjer først. Maksimum ventetid er spesifisert av
pause
, og denne verdien uttrykkes ienhet
enheter spesifisert avTimeUnit
enum; for eksempel,TimeUnit.SECONDS
. Denne metoden kasterjava.lang.InterruptedException
når den gjeldende tråden blir avbrutt. Det kommer tilbake ekte når eksekutøren avsluttes og falsk når tidsavbruddet går før opphør. - boolsk isShutdown () returnerer ekte når utføreren er stengt.
- ugyldig nedleggelse () starter en ordnet nedstengning der tidligere innsendte oppgaver utføres, men ingen nye oppgaver aksepteres.
- Fremtidig innlevering (Callable task) sender en verdi-returoppgave for utføring og returnerer a
Framtid
som representerer de ventende resultatene av oppgaven. - Fremtidig innlevering (kjørbar oppgave) sender inn en
Kjørbar
oppgave for utførelse og retur aFramtid
som representerer den oppgaven.
De Framtid
grensesnitt representerer resultatet av en asynkron beregning. Resultatet er kjent som en framtid fordi den vanligvis ikke vil være tilgjengelig før et øyeblikk i fremtiden. Du kan påkalle metoder for å avbryte en oppgave, returnere resultatene til en oppgave (venter på ubestemt tid eller til en tidsavbrudd for å gå når oppgaven ikke er ferdig), og bestemme om en oppgave er kansellert eller er ferdig.
De Kan kalles
grensesnittet ligner på Kjørbar
grensesnitt ved at det gir en enkelt metode som beskriver en oppgave å utføre. I motsetning til Kjørbar
s ugyldig kjøre ()
metode, Kan kalles
s V-samtale () kaster Unntak
metoden kan returnere en verdi og kaste et unntak.
Executor fabrikk metoder
På et tidspunkt vil du skaffe deg en eksekutor. The Executor framework leverer Utførere
bruksklasse for dette formålet. Utførere
tilbyr flere fabrikkmetoder for å skaffe forskjellige typer utførere som tilbyr spesifikke retningslinjer for trådutførelse. Her er tre eksempler:
- ExecutorService newCachedThreadPool () oppretter et trådbasseng som oppretter nye tråder etter behov, men som gjenbruker tidligere konstruerte tråder når de er tilgjengelige. Tråder som ikke har blitt brukt på 60 sekunder, avsluttes og fjernes fra hurtigbufferen. Denne trådgruppen forbedrer vanligvis ytelsen til programmer som utfører mange kortvarige asynkrone oppgaver.
- ExecutorService newSingleThreadExecutor () oppretter en utfører som bruker en enkelt arbeidertråd som driver en ubegrenset kø - oppgaver legges til i køen og utføres sekvensielt (ikke mer enn én oppgave er aktiv samtidig). Hvis denne tråden avsluttes ved feil under utførelse før avslutning av utføreren, vil en ny tråd opprettes for å ta plass når påfølgende oppgaver må utføres.
- ExecutorService newFixedThreadPool (int nThreads) oppretter en trådgruppe som gjenbruker et fast antall tråder som opererer utenfor en delt ubundet kø. På det meste
nTråder
tråder behandler aktivt oppgaver. Hvis flere oppgaver sendes inn når alle tråder er aktive, venter de i køen til en tråd er tilgjengelig. Hvis en tråd slutter ved feil under utførelse før nedleggelse, vil en ny tråd opprettes for å ta plass når påfølgende oppgaver må utføres. Bassengets tråder eksisterer til utføreren blir stengt.
Utøvende rammeverk tilbyr flere typer (for eksempel PlanlagtExecutorService
grensesnitt), men typene du sannsynligvis vil jobbe med er ofte ExecutorService
, Framtid
, Kan kalles
, og Utførere
.
Se java.util.concurrent
Javadoc for å utforske flere typer.
Arbeide med Executor-rammeverket
Du vil finne at Executor-rammeverket er ganske enkelt å jobbe med. I oppføring 2 har jeg brukt Leder
og Utførere
for å erstatte servereksemplet fra liste 1 med et mer skalerbart trådbasert alternativ.
Oppføring 2. Server.java (Versjon 2)
importere java.io.IOException; importere java.net.ServerSocket; importere java.net.Socket; importere java.util.concurrent.Executor; importere java.util.concurrent.Executors; class Server {static Executor pool = Executors.newFixedThreadPool (5); public static void main (String [] args) kaster IOException {ServerSocket socket = new ServerSocket (9000); while (true) {final Socket s = socket.accept (); Runnable r = new Runnable () {@Override public void run () {doWork (s); }}; basseng.utfør (r); }} statisk ugyldig doWork (stikkontakter) {}}
Oppføring 2 bruker newFixedThreadPool (int)
for å skaffe en thread pool-basert eksekutor som bruker fem tråder på nytt. Det erstatter også ny tråd (r) .start ();
med basseng.utfør (r);
for å utføre kjørbare oppgaver via noen av disse trådene.
Oppføring 3 presenterer et annet eksempel der en applikasjon leser innholdet på en vilkårlig webside. Den sender de resulterende linjene eller en feilmelding hvis innholdet ikke er tilgjengelig innen maksimalt fem sekunder.