Programmering

Moderne trådning: En Java-samtidigprimer

Meget af, hvad der er at lære om programmering med Java-tråde, har ikke ændret sig dramatisk i forhold til udviklingen af ​​Java-platformen, men det har ændret sig trinvist. I denne Java-tråde-primer rammer Cameron Laird nogle af de høje (og lave) punkter i tråde som en samtidig programmeringsteknik. Få et overblik over, hvad der er vedvarende udfordrende ved multitrådet programmering, og find ud af, hvordan Java-platformen har udviklet sig til at imødekomme nogle af udfordringerne.

Samtidighed er blandt de største bekymringer for nybegyndere i Java-programmering, men der er ingen grund til at lade det skræmme dig. Ikke kun er fremragende dokumentation tilgængelig (vi udforsker flere kilder i denne artikel), men Java-tråde er blevet lettere at arbejde med, efterhånden som Java-platformen har udviklet sig. For at lære at udføre multitrådet programmering i Java 6 og 7, har du bare brug for nogle byggesten. Vi starter med disse:

  • Et simpelt gevindprogram
  • Trådning handler kun om hastighed, ikke?
  • Udfordringer ved Java-samtidighed
  • Hvornår skal jeg bruge Runnable
  • Når gode tråde går dårligt
  • Hvad er nyt i Java 6 og 7
  • Hvad er det næste for Java-tråde

Denne artikel er en nybegynderundersøgelse af Java-trådningsteknikker, herunder links til nogle af JavaWorlds hyppigst læste indledende artikler om multitrådet programmering. Start dine motorer, og følg linkene ovenfor, hvis du er klar til at lære om Java-threading i dag.

Et simpelt gevindprogram

Overvej følgende Java-kilde.

Fortegnelse 1. FirstThreadingExample

klasse FirstThreadingExample {public static void main (String [] args) {// Det andet argument er en forsinkelse mellem // successive output. Forsinkelsen er // målt i millisekunder. "10" betyder for eksempel // "udskriv en linje hvert // hundrede sekund". EksempelTråd mt = nyt EksempelTråd ("A", 31); EksempelTråd mt2 = nyt EksempelTråd ("B", 25); EksempelTråd mt3 = nyt EksempelTråd ("C", 10); mt.start (); mt2.start (); mt3.start (); }} klasse EksempelTråd udvider tråd {privat int forsinkelse; public ExampleThread (strengetiket, int d) {// Giv denne tråd en // navn: "tråd 'LABEL'". super ("thread" "+ label +" '"); forsinkelse = d; } public void run () {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); } fange (InterruptedException ie) {// Dette ville være en overraskelse. }}}}

Kompilér og kør nu denne kilde som med andre Java-kommandolinjeprogrammer. Du får vist output, der ser sådan ud:

Liste 2. Output af et gevindprogram

Linje # 1 fra tråd 'A' Linje nr. 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 programmør!

Nå, okay, måske ikke så hurtigt. Så lille som programmet i Listing 1 er, indeholder det nogle finesser, der fortjener vores opmærksomhed.

Tråde og ubestemmelighed

En typisk læringscyklus med programmering består af fire faser: (1) Undersøg nyt koncept; (2) udføre prøveprogram (3) sammenligne output med forventning; og (4) gentages, indtil de to matcher. Bemærk dog, at jeg tidligere sagde output for FirstThreadingExample ville se "noget ud som" Listing 2. Så det betyder, at din produktion kan være forskellig fra min, linje for linje. Hvad er der? at om?

I de enkleste Java-programmer er der en garanti for ordre på udførelse: den første linje ind hoved () udføres først, derefter den næste osv. med passende sporing ind og ud af andre metoder. Tråd svækker den garanti.

Trådning bringer ny kraft til Java-programmering; du kan opnå resultater med tråde, som du ikke kunne undvære dem. Men den magt kommer på bekostning af bestemmelse. I de enkleste Java-programmer er der en garanti for ordre på udførelse: den første linje ind hoved () udføres først, derefter den næste og så videre med passende sporing ind og ud af andre metoder. Tråd svækker den garanti. I et multitrådet program "Linje nr. 17 fra tråd B"vises muligvis på din skærm før eller efter"Linje nr. 14 fra tråd A, "og rækkefølgen kan variere ved successive henrettelser af det samme program, selv på den samme computer.

Ubestemmelighed kan være ukendt, men det behøver ikke at være foruroligende. Orden for udførelse inden for en tråd forbliver forudsigelig, og der er også fordele forbundet med ubestemmelighed. Du har muligvis oplevet noget lignende, når du arbejder med grafiske brugergrænseflader (GUI'er). Begivenhedslyttere i Swing eller eventhåndterere i HTML er eksempler.

Mens en fuld diskussion af trådsynkronisering ligger uden for denne introduktions omfang, er det let at forklare det grundlæggende.

Overvej for eksempel mekanikken i, hvordan HTML specificerer ... onclick = "myFunction ();" ... for at bestemme den handling, der skal ske, når brugeren klikker. Dette velkendte tilfælde af ubestemmelighed illustrerer nogle af dets fordele. I dette tilfælde, myFunction () udføres ikke på et bestemt tidspunkt med hensyn til andre elementer i kildekoden, men i forhold til slutbrugerens handling. Så ubestemmelighed er ikke kun en svaghed i systemet; det er også en berigelse af udførelsesmodellen, en der giver programmøren nye muligheder for at bestemme sekvens og afhængighed.

Udførelsesforsinkelser og trådklassificering

Du kan lære af FirstThreadingExample ved at eksperimentere med det alene. Prøv at tilføje eller fjerne Eksempel Tråds - det vil sige konstruktøropkald som ... nyt EksempelTråd (etiket, forsinkelse); - og tinker med forsinkes. Grundideen er, at programmet starter tre separate Tråds, som derefter kører uafhængigt indtil afslutningen. For at gøre deres udførelse mere lærerig forsinker hver enkelt en smule mellem de successive linjer, den skriver til output; dette giver de andre tråde en chance for at skrive deres produktion.

Noter det Tråd-baseret programmering kræver generelt ikke håndtering af en Afbrudt undtagelse. Den der er vist i FirstThreadingExample har at gøre med søvn(), snarere end at være direkte relateret til Tråd. Mest Tråd-baseret kilde inkluderer ikke en søvn(); Formålet med søvn() her er det på en enkel måde at modellere opførelsen af ​​langvarige metoder, der findes "i naturen."

Noget andet at bemærke i liste 1 er, at Tråd er en abstrakt klasse, designet til at blive underklasseret. Dens standard løb() metode gør intet, så det skal tilsidesættes i underklassedefinitionen for at opnå noget nyttigt.

Det handler kun om hastighed, ikke?

Så nu kan du se en smule af, hvad der gør programmering med tråde kompliceret. Men det vigtigste punkt i at udholde alle disse vanskeligheder ikke er for at få fart.

Multitrådede programmer lade være medgenerelt færdiggør hurtigere end enkeltgevindede - faktisk kan de være betydeligt langsommere i patologiske tilfælde. Den grundlæggende merværdi ved multitrådede programmer er lydhørhed. Når JVM har flere bearbejdningskerner, eller når programmet bruger lang tid på at vente på flere eksterne ressourcer, f.eks. Netværksrespons, kan multithreading hjælpe programmet med at gennemføre hurtigere.

Tænk på et GUI-program: hvis det stadig reagerer på slutbrugernes point og klik, mens du søger "i baggrunden" efter et matchende fingeraftryk eller genberegner kalenderen til næste års tennisturnering, blev den bygget med samtidighed i tankerne. En typisk samtidig applikationsarkitektur placerer genkendelse og respons på brugerhandlinger i en tråd, der er adskilt fra den beregningstråd, der er tildelt til at håndtere den store back-end-belastning. (Se "Swing threading and the event-dispatch thread" for yderligere illustration af disse principper.)

I din egen programmering vil du sandsynligvis overveje at bruge Tråds under en af ​​disse omstændigheder:

  1. En eksisterende applikation har korrekt funktionalitet, men reagerer ikke til tider. Disse "blokke" har ofte at gøre med eksterne ressourcer uden for din kontrol: tidskrævende databaseforespørgsler, komplicerede beregninger, multimedieafspilning eller netværkssvar med ukontrollabel ventetid.
  2. En beregningsintensiv applikation kunne gøre bedre brug af multicore-værter. Dette kan være tilfældet for nogen, der gengiver kompleks grafik eller simulerer en involveret videnskabelig model.
  3. Tråd udtrykker naturligvis applikationens krævede programmeringsmodel. Antag for eksempel, at du modellerede adfærden hos rushtidsbilchauffører eller bier i en bikube. At implementere hver driver eller bi som en Tråd-relateret objekt kan være praktisk fra et programmeringsmæssigt synspunkt bortset fra alle hensyn til hastighed eller lydhørhed.

Udfordringer ved Java-samtidighed

Den erfarne programmør Ned Batchelder kvitterede for nylig

Nogle mennesker tænker, "når jeg konfronteres med et problem," Jeg ved, jeg bruger tråde, "og derefter to har de erpoblesmer.

Det er sjovt, fordi det så godt modellerer problemet med samtidighed. Som jeg allerede har nævnt, vil multitrådede programmer sandsynligvis give forskellige resultater med hensyn til den nøjagtige rækkefølge eller timing af trådudførelse. Det er foruroligende for programmører, der er uddannet til at tænke i form af reproducerbare resultater, streng bestemmelse og uforanderlig rækkefølge.

Det bliver værre. Forskellige tråde giver måske ikke kun resultater i forskellige ordrer, men de kan hævder på mere essentielle niveauer for resultater. Det er let for en nybegynder at multithreading til tæt() et filhåndtag i en Tråd før en anden Tråd er færdig med alt, hvad den har brug for at skrive.

Test af samtidige programmer

For ti år siden på JavaWorld bemærkede Dave Dyer, at Java-sproget havde en funktion, der så "gennemgribende blev brugt forkert", at han rangerede det som en alvorlig designfejl. Denne funktion var multithreading.

Dyers kommentar fremhæver udfordringen ved at teste multitrådede programmer. Når du ikke længere let kan specificere output fra et program i form af en bestemt rækkefølge af tegn, vil der være en indflydelse på, hvor effektivt du kan teste din gevindkode.

Det korrekte udgangspunkt for at løse de iboende vanskeligheder ved samtidig programmering blev godt udtalt af Heinz Kabutz i hans Java Specialist-nyhedsbrev: erkend, at samtidighed er et emne, som du skal forstå og studere det systematisk. Der er selvfølgelig værktøjer såsom diagramteknikker og formelle sprog, der kan hjælpe. Men det første skridt er at skærpe din intuition ved at øve med enkle programmer som FirstThreadingExample i oversigt 1. Lær derefter så meget du kan om at tråde grundlæggende som disse:

  • Synkronisering og uforanderlige objekter
  • Trådplanlægning og vent / underret
  • Race betingelser og dødvande
  • Trådmonitorer giver eksklusiv adgang, betingelser og påstande
  • JUnit bedste praksis - test af flertrådet kode

Hvornår skal jeg bruge Runnable

Objektorientering i Java definerer enkelt arvede klasser, hvilket har konsekvenser for multithreading-kodning. Til dette punkt har jeg kun beskrevet en anvendelse til Tråd det var baseret på underklasser med en tilsidesat løb(). I et objektdesign, der allerede involverede arv, fungerede dette simpelthen ikke. Du kan ikke samtidig arve fra RenderedObject eller ProductionLine eller MessageQueue langs med Tråd!

Denne begrænsning påvirker mange områder af Java, ikke kun multithreading. Heldigvis er der en klassisk løsning på problemet i form af Kan køres interface. Som forklaret af Jeff Friesen i sin introduktion til threading i 2002, blev Kan køres interface er lavet til situationer, hvor underklassificering Tråd er ikke muligt:

Det Kan køres interface erklærer en enkelt metodesignatur: ugyldig kørsel ();. Denne underskrift er identisk med Tråd's løb() metodesignatur og fungerer som en tråds udførelse. Fordi Kan køres er en grænseflade, kan enhver klasse implementere grænsefladen ved at vedhæfte en redskaber klausul til klasseoverskriften og ved at give en passende løb() metode. Ved udførelsestid kan programkode oprette et objekt eller kører, fra denne klasse og videregive henvisningens henvisning til en passende Tråd konstruktør.

Så for de klasser, der ikke kan udvides Tråd, skal du oprette en runnable for at drage fordel af multithreading. Semantisk, hvis du laver programmering på systemniveau, og din klasse er i en is-en relation til Tråd, så skal du underklasse direkte fra Tråd. Men mest anvendelse af multitråd på applikationsniveau er afhængig af sammensætning og definerer således a Kan køres kompatibel med applikationens klassediagram. Heldigvis tager det kun en ekstra linje eller to at kode ved hjælp af Kan køres interface, som vist i liste 3 nedenfor.