Samtidighed i Java

Java - programmeringssproget og JVM ( Java Virtual Machine ) er designet til at understøtte parallel computing , og alle beregninger udføres i sammenhæng med en tråd . Flere tråde kan dele objekter og ressourcer; hver tråd udfører sine egne instruktioner (kode), men kan potentielt få adgang til ethvert objekt i programmet. Det er programmørens ansvar at koordinere (eller " synkronisere ") tråde under læse- og skriveoperationer på delte objekter. Trådsynkronisering er nødvendig for at sikre, at kun én tråd kan få adgang til et objekt ad gangen, og for at forhindre tråde i at få adgang til ufuldstændigt opdaterede objekter, mens en anden tråd arbejder på dem. Java-sproget har indbygget trådsynkroniseringsstøttekonstruktioner.

Processer og tråde

De fleste implementeringer af Java Virtual Machine bruger en enkelt proces til at køre programmet, og i Java-programmeringssproget er parallel computing oftest forbundet med tråde . Tråde kaldes nogle gange lette processer .

Stream objekter

Tråde deler procesressourcer, såsom hukommelse og åbne filer, indbyrdes. Denne tilgang fører til effektiv, men potentielt problematisk kommunikation. Hver applikation har mindst én kørende tråd. Tråden, hvorfra udførelsen af ​​programmet starter, kaldes hoved eller hoved . Hovedtråden er i stand til at skabe yderligere tråde i form af objekter Runnableeller Callable. (Grænsefladen Callableer ens, Runnableidet begge er designet til klasser, der vil blive instansieret på en separat tråd. Runnable, men returnerer ikke et resultat og kan ikke afgive en markeret undtagelse .)

Hver tråd kan planlægges til at køre på en separat CPU-kerne, bruge tidsudskæring på en enkelt processorkerne eller bruge tidsudskæring på flere processorer. I de sidste to tilfælde vil systemet periodisk skifte mellem tråde og skiftevis tillade den ene eller den anden tråd at udføre. Denne ordning kaldes pseudo-parallelisme. Der er ingen universel løsning, der vil sige præcis, hvordan Java-tråde vil blive konverteret til OS native-tråde. Det afhænger af den specifikke JVM-implementering.

I Java er en tråd repræsenteret som et underordnet objekt af Thread. Denne klasse indkapsler standard gevindmekanismer. Tråde kan administreres enten direkte eller gennem abstrakte mekanismer såsom Executor og samlinger fra java.util.concurrent-pakken.

Kører en tråd

Der er to måder at starte en ny tråd på:

  • Implementering af Runnable-grænsefladen
public class HelloRunnable implementerer Runnable { public void run () { System . ud . println ( "Hej fra tråden!" ); } public static void main ( String [] args ) { ( new Thread ( new HelloRunnable ())). start (); } }
  • Arv fra trådklasse
public class HelloThread udvider Thread { public void run () { System . ud . println ( "Hej fra tråden!" ); } public static void main ( String [] args ) { ( new HelloThread ()). start (); } } Afbryder

Et interrupt er en indikation til en tråd om, at den skal stoppe det aktuelle arbejde og gøre noget andet. En tråd kan sende en interrupt ved at kalde objektets interrupt()-Thread metode, hvis den skal afbryde dens tilknyttede tråd. Afbrydelsesmekanismen implementeres ved hjælp af den interne flagafbrydelsesstatus (afbrydelsesflag) for klassen Thread. Kaldning af Thread.interrupt() hæver dette flag. Efter konvention vil enhver metode, der ender med en InterruptedException , nulstille interrupt-flaget. Der er to måder at kontrollere, om dette flag er indstillet. Den første måde er at kalde bool isInterrupted()- metoden for trådobjektet , den anden måde er at kalde den statiske bool Thread.interrupted()-metoden . Den første metode returnerer tilstanden for afbrydelsesflaget og lader dette flag være urørt. Den anden metode returnerer flagets tilstand og nulstiller den. Bemærk, at Thread.interrupted()  er en statisk metode af klassen Thread, og kalder den returnerer værdien af ​​interrupt-flaget for den tråd, hvorfra den blev kaldt.

Venter på færdiggørelse

Java giver en mekanisme, der tillader en tråd at vente på, at en anden tråd er færdig med at udføre. Til dette bruges metoden Thread.join() .

Dæmoner

I Java afsluttes en proces, når dens sidste tråd afsluttes. Selvom main()-metoden allerede er fuldført, men de tråde, den skabte, stadig kører, vil systemet vente på, at de er færdige. Denne regel gælder dog ikke for en speciel slags tråd - dæmoner. Hvis den sidste normale tråd i processen er afsluttet, og der kun er dæmontråde tilbage, vil de blive tvangsafsluttet, og processen vil afslutte. Oftest bruges daemon-tråde til at udføre baggrundsopgaver, der servicerer en proces i løbet af dens levetid.

At erklære en tråd som en dæmon er ret simpel - du skal kalde dens setDaemon(true) metode før du starter tråden ; Du kan kontrollere, om en tråd er en dæmon ved at kalde dens booleske isDaemon()- metode .

Undtagelser

En smidt og ubehandlet undtagelse vil få tråden til at afslutte. Hovedtråden vil automatisk udskrive undtagelsen til konsollen, og brugeroprettede tråde kan kun gøre det ved at registrere en handler. [1] [2]

Hukommelsesmodel

Java-hukommelsesmodellen [1] beskriver interaktionen mellem tråde gennem hukommelsen i programmeringssproget Java. Ofte, på moderne computere, udføres kode ikke i den rækkefølge, den er skrevet i, for hastighedens skyld. Permutationen udføres af compileren , processoren og hukommelsesundersystemet . Java-programmeringssproget garanterer ikke atomicitet af operationer og sekventiel konsistens ved læsning eller skrivning af felter af delte objekter. Denne løsning frigør compilerens hænder og tillader optimeringer (såsom registerallokering , fjernelse af almindelige underudtryk og eliminering af redundante læseoperationer ) baseret på permutation af hukommelsesadgangsoperationer. [3]

Synkronisering

Tråde kommunikerer ved at dele adgang til felter og objekter, der refereres til af felter. Denne form for kommunikation er ekstremt effektiv, men den gør to slags fejl mulige: trådinterferens og hukommelseskonsistensfejl. For at forhindre deres forekomst er der en synkroniseringsmekanisme.

Genarrangering (omarrangering, omarrangering) manifesterer sig i forkert synkroniserede multitrådede programmer, hvor en tråd kan observere effekter produceret af andre tråde, og sådanne programmer kan være i stand til at detektere, at de opdaterede værdier af variabler bliver synlige for andre tråde i en anden tråd. rækkefølge end angivet i kildekoden.

For at synkronisere tråde i Java bruges skærme , som er en mekanisme på højt niveau, der tillader kun én tråd ad gangen at udføre en kodeblok beskyttet af en skærm. Skærmenes opførsel betragtes i form af låse ; Hvert objekt har en lås tilknyttet.

Synkronisering har flere aspekter. Den mest forståelige er gensidig udelukkelse - kun én tråd kan eje en skærm, så synkronisering på skærmen betyder, at når én tråd kommer ind i en synkroniseret blok beskyttet af skærmen, kan ingen anden tråd komme ind i blokken beskyttet af denne skærm før den første tråd afslutter den synkroniserede blok.

Men synkronisering er mere end blot gensidig udelukkelse. Synkronisering sikrer, at data skrevet til hukommelsen før eller inden for en synkroniseret blok bliver synlige for andre tråde, der er synkroniseret på den samme skærm. Efter at vi har forladt den synkroniserede blok, frigiver vi monitoren, som har den effekt at skylle cachen ind i hovedhukommelsen, så skrivningerne lavet af vores tråd kan være synlige for andre tråde. Inden vi kan gå ind i den synkroniserede blok, anskaffer vi monitoren, hvilket har den effekt at ugyldiggøre den lokale processorcache, så variablerne indlæses fra hovedhukommelsen. Så kan vi se alle de poster, der er gjort synlige af den tidligere udgivelse af skærmen. (JSR 133)

En læse-skrive på et felt er en atomoperation, hvis feltet enten er erklæret flygtigt eller beskyttet af en unik lås erhvervet før nogen læse-skrive.

Låse og synkroniserede blokke

Effekten af ​​gensidig udelukkelse og trådsynkronisering opnås ved at indtaste en synkroniseret blok eller metode, der erhverver låsen implicit, eller ved eksplicit at erhverve låsen (såsom ReentrantLockfra java.util.concurrent.locks-pakken). Begge tilgange har samme effekt på hukommelsesadfærd. Hvis alle adgangsforsøg til et bestemt felt er beskyttet af den samme lås, så er læse-skrive-operationer af dette felt atomiske .

Flygtige felter

Når det anvendes på felter, volatilegaranterer søgeordet:

  1. (I alle versioner af Java) Adgange til volatile-variable er ordnet globalt. Dette betyder, at hver tråd , der får adgang til volatile-feltet, vil læse dens værdi, før den fortsætter, i stedet for (hvis muligt) at bruge den cachelagrede værdi. (Adgange til volatile-variabler kan ikke omarrangeres med hinanden, men de kan omarrangeres med adgang til almindelige variabler. Dette negerer anvendeligheden volatileaf ​​-felter som et middel til at signalere fra en tråd til en anden.)
  2. (I Java 5 og nyere) At skrive til et felt har volatilesamme effekt på hukommelsen som monitor release , mens læsning har samme effekt som monitor acquisition .  Adgang til feltet etablerer " sker før " - forholdet . [4] Grundlæggende er denne relation en garanti for, at alt det, der var synligt for tråden , da den skrev til feltet , bliver synligt for tråden , når den læser . volatile AvolatilefBf

Volatile-felter er atomare. At læse fra volatileet -felt har samme effekt som at anskaffe en lås: dataene i arbejdshukommelsen erklæres ugyldige, og volatile-feltets værdi genlæses fra hukommelsen. At skrive til et volatile-felt har samme effekt på hukommelsen som at frigive en lås: volatile-feltet skrives straks til hukommelsen.

Sidste felter

Et felt, der er erklæret endeligt, kaldes endeligt og kan ikke ændres efter initialisering. De sidste felter af et objekt initialiseres i dets konstruktør. Hvis konstruktøren følger visse simple regler, så vil den korrekte værdi af det endelige felt være synlig for andre tråde uden synkronisering. En simpel regel er, at denne reference ikke må forlade konstruktøren, før den er afsluttet.

Historie

Startende med JDK 1.2 inkluderer Java et standardsæt af Java Collections Framework- samlingsklasser .

Doug Lee , som også bidrog til implementeringen af ​​Java Collections Framework, udviklede samtidighedspakken , som omfatter flere synkroniseringsprimitiver og et stort antal samlingsrelaterede klasser. [5] Arbejdet med det blev fortsat som en del af JSR 166 [6] under ledelse af Doug Lee .

JDK 5.0- udgivelsen indeholdt mange tilføjelser og forklaringer til Java samtidighedsmodellen. For første gang blev samtidigheds-API'erne udviklet af JSR 166 inkluderet i JDK. JSR 133 gav støtte til veldefinerede atomoperationer i et multithreaded/multiprocessor-miljø.

Både Java SE 6 og Java SE 7 bringer ændringer og tilføjelser til JSR 166 API.

Se også

Noter

  1. Oracle Interface Thread.UncaughtExceptionHandler . Hentet 10. maj 2014. Arkiveret fra originalen 12. maj 2014.
  2. Silent Thread død fra ubehandlede undtagelser . readjava.com . Hentet 10. maj 2014. Arkiveret fra originalen 12. maj 2014.
  3. Herlihy, Maurice og Nir Shavit. "Kunsten at programmere med flere processorer." PODC. Vol. 6. 2006.
  4. Afsnit 17.4.4: Synchronization Order The Java® Language Specification, Java SE 7 Edition . Oracle Corporation (2013). Hentet 12. maj 2013. Arkiveret fra originalen 3. februar 2021.
  5. Doug Lee . Oversigt over pakken util.concurrent Release 1.3.4 . — « Bemærk: Ved frigivelse af J2SE 5.0 går denne pakke i vedligeholdelsestilstand: Kun væsentlige rettelser frigives. J2SE5-pakken java.util.concurrent inkluderer forbedrede, mere effektive, standardiserede versioner af hovedkomponenterne i denne pakke. ". Hentet 1. januar 2011. Arkiveret fra originalen 18. december 2020.
  6. JSR 166: Concurrency Utilities (link ikke tilgængeligt) . Hentet 3. november 2015. Arkiveret fra originalen 3. november 2016. 

Links

  • Goetz, Brian; Joshua Bloch; Joseph Bowbeer; Doug Lea; David Holmes Tim Peierls. Java samtidighed i praksis  (neopr.) . - Addison Wesley , 2006. - ISBN 0-321-34960-1 .
  • Leah, Doug. Samtidig programmering i Java : Designprincipper og mønstre  . - Addison Wesley , 1999. - ISBN 0-201-31009-0 .

Links til eksterne ressourcer