Programmering

Java 101: Java-samtidighet uten smerte, del 1

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(), og notifyAll ()) 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 ExecutorServicemetodene 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 i enhet enheter spesifisert av TimeUnit enum; for eksempel, TimeUnit.SECONDS. Denne metoden kaster java.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 a Framtid 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ørbars ugyldig kjøre () metode, Kan kalless 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.

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