Programmering

Modern threading: En Java-samtidighetsgrunning

Mye av det som er å lære om programmering med Java-tråder, har ikke endret seg dramatisk i løpet av utviklingen av Java-plattformen, men det har endret seg trinnvis. I denne Java-tråderprimeren treffer Cameron Laird noen av de høye (og lave) punktene i tråder som en samtidig programmeringsteknikk. Få en oversikt over hva som er flerårig utfordrende med flertrådet programmering, og finn ut hvordan Java-plattformen har utviklet seg for å møte noen av utfordringene.

Samtidighet er blant de største bekymringene for nykommere i Java-programmering, men det er ingen grunn til å la det skremme deg. Ikke bare er utmerket dokumentasjon tilgjengelig (vi vil utforske flere kilder i denne artikkelen), men Java-tråder har blitt lettere å jobbe med etter hvert som Java-plattformen har utviklet seg. For å lære å gjøre flertrådet programmering i Java 6 og 7, trenger du egentlig bare noen byggesteiner. Vi begynner med disse:

  • Et enkelt gjenget program
  • Tråding handler om fart, ikke sant?
  • Utfordringer med Java-samtidighet
  • Når skal du bruke Runnable
  • Når gode tråder går dårlig
  • Hva er nytt i Java 6 og 7
  • Hva er neste for Java-tråder

Denne artikkelen er en nybegynnerundersøkelse av Java-trådteknikker, inkludert lenker til noen av JavaWorlds mest innledende artikler om flertrådet programmering. Start motorene dine og følg lenkene ovenfor hvis du er klar til å begynne å lære om Java-threading i dag.

Et enkelt gjenget program

Tenk på følgende Java-kilde.

Oppføring 1. FirstThreadingExample

class FirstThreadingExample {public static void main (String [] args) {// Det andre argumentet er en forsinkelse mellom // suksessive utganger. Forsinkelsen er // målt i millisekunder. "10", for eksempel // betyr, "skriv ut en linje hvert // hundrendedels sekund". EksempelTråd mt = nytt EksempelTråd ("A", 31); EksempelTråd mt2 = nytt EksempelTråd ("B", 25); EksempelTråd mt3 = nytt EksempelTråd ("C", 10); mt.start (); mt2.start (); mt3.start (); }} klasse EksempelTråd utvider tråd {privat int forsinkelse; public ExampleThread (strengetikett, int d) {// Gi denne tråden et // navn: "tråd 'LABEL'". super ("tråd" "+ etikett +" '"); forsinkelse = d; } offentlig ugyldig kjøring () {for (int count = 1, row = 1; row <20; row ++, count ++) {prøv {System.out.format ("Linje #% d fra% s \ n", count, getName ()); Tråd.strømTråd (). Søvn (forsinkelse); } fangst (InterruptedException ie) {// Dette ville være en overraskelse. }}}}

Nå kan du kompilere og kjøre denne kilden som for alle andre Java-kommandolinjeprogrammer. Du ser utdata som ser omtrent slik ut:

Oppføring 2. Utdata fra et gjenget program

Linje # 1 fra tråd 'A' Linje # 1 fra tråd 'C' Linje # 1 fra tråd 'B' Linje # 2 fra tråd 'C' Linje # 3 fra tråd 'C' Linje # 2 fra tråd 'B' Linje # 4 fra tråd 'C' ... Linje # 17 fra tråd 'B' Linje # 14 fra tråd 'A' Linje # 18 fra tråd 'B' Linje # 15 fra tråd 'A' Linje # 19 fra tråd 'B' Linje # 16 fra tråd 'A' Linje # 17 fra tråd 'A' Linje # 18 fra tråd 'A' Linje # 19 fra tråd 'A'

Det er det - du er en Java Tråd Programmerer!

Vel, ok, kanskje ikke så fort. Så lite som programmet i Listing 1 er, inneholder det noen finesser som fortjener vår oppmerksomhet.

Tråder og ubestemmelighet

En typisk læringssyklus med programmering består av fire trinn: (1) Studer nytt konsept; (2) utføre prøveprogram; (3) sammenligne produksjonen til forventningene; og (4) gjenta til de to matchene. Vær imidlertid oppmerksom på at jeg tidligere sa utdataene for FirstThreadingExample vil se "ut som" Listing 2. Så det betyr at produksjonen din kan være forskjellig fra min, linje for linje. Hva er det? at Om?

I de enkleste Java-programmene er det en garanti for ordre på utførelse: første linje inn hoved() vil bli utført først, deretter neste og så videre, med passende sporing inn og ut av andre metoder. Tråd svekker den garantien.

Tråding gir ny kraft til Java-programmering; du kan oppnå resultater med tråder som du ikke kunne gjort uten dem. Men den kraften kommer på bekostning av bestemmelse. I de enkleste Java-programmene er det en garanti for ordre på utførelse: første linje inn hoved() vil bli utført først, deretter neste og så videre, med passende sporing inn og ut av andre metoder. Tråd svekker den garantien. I et flertrådet program, "Linje nr. 17 fra tråd B"kan vises på skjermen din før eller etter"Linje # 14 fra tråd A, "og rekkefølgen kan variere ved påfølgende kjøringer av det samme programmet, til og med på samme datamaskin.

Ubestemthet kan være ukjent, men det trenger ikke være forstyrrende. Ordre på utførelse innenfor en tråd forblir forutsigbar, og det er også fordeler forbundet med ubestemmelighet. Du har kanskje opplevd noe lignende når du arbeider med grafiske brukergrensesnitt (GUI). Eventlyttere i Swing eller eventbehandlere i HTML er eksempler.

Mens en fullstendig diskusjon om trådsynkronisering ligger utenfor omfanget av denne introduksjonen, er det enkelt å forklare det grunnleggende.

Tenk for eksempel på mekanikken til hvordan HTML spesifiserer ... onclick = "myFunction ();" ... for å bestemme handlingen som vil skje etter at brukeren klikker. Dette kjente tilfellet med ubestemthet illustrerer noen av fordelene. I dette tilfellet, myFunction () blir ikke utført på et bestemt tidspunkt med hensyn til andre elementer i kildekoden, men i forhold til sluttbrukerens handling. Så ubestemmelighet er ikke bare en svakhet i systemet; det er også en berikelse av utførelsesmodellen, en som gir programmereren nye muligheter til å bestemme sekvens og avhengighet.

Forsinkelser i utførelsen og underklassering av tråd

Du kan lære av FirstThreadingExample ved å eksperimentere med det på egen hånd. Prøv å legge til eller fjerne Eksempel på tråds - det vil si konstruktøranrop som ... nytt EksempelTråd (etikett, forsinkelse); - og fikle med forsinkelses. Grunnideen er at programmet starter tre separate Tråds, som deretter kjører uavhengig til fullføring. For å gjøre utførelsen mer lærerik, forsinker hver enkelt litt mellom de påfølgende linjene den skriver for å sende ut dette gir de andre trådene en sjanse til å skrive deres produksjon.

Noter det Tråd-basert programmering krever generelt ikke håndtering av en InterruptedException. Den som vises i FirstThreadingExample har å gjøre med søvn(), snarere enn å være direkte relatert til Tråd. Mest Tråd-basert kilde inkluderer ikke en søvn(); meningen med søvn() her er å modellere, på en enkel måte, oppførselen til langvarige metoder som er funnet "i naturen."

Noe annet å legge merke til i Listing 1 er at Tråd er en abstrakt klasse, designet for å bli underklassert. Standard løpe() metoden gjør ingenting, så må overstyres i underklassedefinisjonen for å oppnå noe nyttig.

Dette handler om fart, ikke sant?

Så nå kan du se litt av hva som gjør programmering med tråder komplisert. Men hovedpoenget med å tåle alle disse vanskelighetene ikke er det for å få fart.

Flertrådede programmer ikkegenerelt sett fullføre raskere enn enkeltrådede - faktisk kan de være betydelig langsommere i patologiske tilfeller. Den grunnleggende merverdien til flertrådede programmer er respons. Når flere prosessorkjerner er tilgjengelige for JVM, eller når programmet bruker betydelig tid på å vente på flere eksterne ressurser, for eksempel nettverksresponser, kan multitråding hjelpe programmet med å fullføre raskere.

Tenk på et GUI-program: hvis det fremdeles svarer på sluttbrukerpoeng og klikker mens du søker "i bakgrunnen" etter et matchende fingeravtrykk eller beregner kalenderen for neste års tennisturnering på nytt, så ble den bygget med tanke på samtidighet. En typisk samtidig applikasjonsarkitektur setter gjenkjenning og respons på brukerhandlinger i en tråd som er separat fra beregningstråden som er tilordnet for å håndtere den store back-end-belastningen. (Se "Swing threading and the event-dispatch thread" for ytterligere illustrasjon av disse prinsippene.)

I din egen programmering vil du derfor mest sannsynlig vurdere å bruke Tråds under en av disse omstendighetene:

  1. En eksisterende applikasjon har riktig funksjonalitet, men reagerer ikke til tider. Disse "blokkene" har ofte å gjøre med eksterne ressurser utenfor din kontroll: tidkrevende databasespørsmål, kompliserte beregninger, multimediaavspilling eller nettverksresponser med ukontrollabel ventetid.
  2. En beregningsintensiv applikasjon kan bedre utnytte multicore-verter. Dette kan være tilfelle for noen som gjengir kompleks grafikk eller simulerer en involvert vitenskapelig modell.
  3. Tråd uttrykker naturlig applikasjonens nødvendige programmeringsmodell. Anta for eksempel at du modellerte oppførselen til rushtidsbilførere eller bier i en bikube. Å implementere hver driver eller bi som en Tråd-relatert objekt kan være praktisk fra et programmeringssynspunkt, bortsett fra alle hensyn til hastighet eller respons.

Utfordringer med Java-samtidighet

Den erfarne programmereren Ned Batchelder kvitterte nylig

Noen mennesker, når de blir konfrontert med et problem, tenker: "Jeg vet, jeg bruker tråder," og deretter to har de erpoblesmer.

Det er morsomt fordi det så godt modellerer problemet med samtidighet. Som jeg allerede har nevnt, vil flertrådede programmer sannsynligvis gi forskjellige resultater når det gjelder den nøyaktige sekvensen eller tidspunktet for kjøring av tråden. Det er plagsomt for programmerere, som er opplært til å tenke i form av reproduserbare resultater, streng bestemmelse og uforanderlig sekvens.

Det blir verre. Ulike tråder kan ikke bare gi resultater i forskjellige ordrer, men de kan strides på mer essensielle nivåer for resultater. Det er enkelt for en nykommer å multitråde Lukk() et filhåndtak i ett Tråd før en annen Tråd har fullført alt det trenger å skrive.

Testing av samtidige programmer

For ti år siden på JavaWorld, bemerket Dave Dyer at Java-språket hadde en funksjon så "gjennomgripende brukt feil" at han rangerte den som en alvorlig designfeil. Denne funksjonen var flertråding.

Dyers kommentar fremhever utfordringen med å teste flertrådede programmer. Når du ikke lenger enkelt kan spesifisere utdataene til et program i form av en bestemt sekvens av tegn, vil det ha innvirkning på hvor effektivt du kan teste den gjengede koden din.

Det riktige utgangspunktet for å løse de indre problemene med samtidig programmering ble godt uttalt av Heinz Kabutz i hans Java Specialist-nyhetsbrev: erkjenn at samtidighet er et tema du bør forstå og studere det systematisk. Det er selvfølgelig verktøy som diagrammingsteknikker og formelle språk som vil hjelpe. Men det første trinnet er å skjerpe intuisjonen din ved å øve med enkle programmer som FirstThreadingExample i oppføring 1. Lær så mye du kan om å trekke grunnleggende som disse:

  • Synkronisering og uforanderlige gjenstander
  • Trådplanlegging og vent / varsle
  • Race forhold og fastlåst
  • Trådmonitorer for eksklusiv tilgang, betingelser og påstander
  • JUnit beste praksis - testing av flertrådet kode

Når skal du bruke Runnable

Objektorientering i Java definerer enkelt arvede klasser, noe som har konsekvenser for multitradingskoding. Til dette punktet har jeg bare beskrevet en bruk for Tråd som var basert på underklasser med overstyrt løpe(). I et objektdesign som allerede innebar arv, ville dette ganske enkelt ikke fungere. Du kan ikke arve samtidig fra RenderedObject eller Produksjonslinje eller MessageQueue ved siden av Tråd!

Denne begrensningen påvirker mange områder av Java, ikke bare multithreading. Heldigvis er det en klassisk løsning på problemet, i form av Kjørbar grensesnitt. Som forklart av Jeff Friesen i introduksjonen til threading i 2002, har Kjørbar grensesnitt er laget for situasjoner der underklassering Tråd er ikke mulig:

De Kjørbar grensesnitt erklærer en enkelt metodesignatur: ugyldig kjøre ();. Den signaturen er identisk med Tråds løpe() metodesignatur og fungerer som en tråds utførelse. Fordi Kjørbar er et grensesnitt, kan enhver klasse implementere det grensesnittet ved å feste et redskaper klausul til kursoverskriften og ved å gi en passende løpe() metode. Ved utførelsestid kan programkode opprette et objekt, eller kjørbar, fra den klassen og gi referansen til kjører til en passende Tråd konstruktør.

Så for de klassene som ikke kan utvides Tråd, må du opprette en runnable for å dra nytte av multithreading. Semantisk, hvis du gjør programmering på systemnivå og klassen din er i et forhold til Tråd, så bør du underklasse direkte fra Tråd. Men mest bruk på multitråding på applikasjonsnivå er avhengig av komposisjon, og definerer dermed en Kjørbar kompatibel med applikasjonens klassediagram. Heldigvis tar det bare en ekstra linje eller to å kode med Kjørbar grensesnitt, som vist i liste 3 nedenfor.

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