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.
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 .
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ådDer er to måder at starte en ny tråd på:
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ørelseJava 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æmonerI 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 .
UndtagelserEn 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]
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]
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 blokkeEffekten 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 felterNår det anvendes på felter, volatilegaranterer søgeordet:
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 felterEt 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.
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.