Samtidighet i Java

Programmeringsspråket Java och JVM ( Java Virtual Machine ) är designade för att stödja parallell beräkning , och alla beräkningar utförs i en tråd . Flera trådar kan dela objekt och resurser; varje tråd kör sina egna instruktioner (kod), men kan potentiellt komma åt vilket objekt som helst i programmet. Det är programmerarens ansvar att koordinera (eller " synkronisera "") trådar under läs- och skrivoperationer på delade objekt. Trådsynkronisering behövs för att säkerställa att endast en tråd kan komma åt ett objekt åt gången och för att förhindra att trådar kommer åt ofullständigt uppdaterade objekt medan en annan tråd arbetar med dem. Java-språket har inbyggda trådsynkroniseringsstödkonstruktioner.

Processer och trådar

De flesta implementeringar av Java Virtual Machine använder en enda process för att köra programmet, och i programmeringsspråket Java är parallell beräkning oftast associerad med trådar . Trådar kallas ibland för lätta processer .

Streama objekt

Trådar delar processresurser, såsom minne och öppna filer, sinsemellan. Detta tillvägagångssätt leder till effektiv men potentiellt problematisk kommunikation. Varje applikation har minst en löpande tråd. Tråden från vilken körningen av programmet börjar kallas main eller main . Huvudtråden kan skapa ytterligare trådar i form av objekt Runnableeller Callable. (Gränssnittet Callableär liknande Runnablegenom att båda är designade för klasser som kommer att instansieras på en separat tråd. Runnable, men returnerar inte ett resultat och kan inte skicka ett markerat undantag .)

Varje tråd kan schemaläggas för att köras på en separat CPU-kärna, använda tidsdelning på en enda processorkärna eller använda tidsdelning på flera processorer. I de två sista fallen kommer systemet med jämna mellanrum att växla mellan trådar och växelvis låta den ena eller den andra tråden köras. Detta schema kallas pseudo-parallelism. Det finns ingen universell lösning som skulle säga exakt hur Java-trådar kommer att konverteras till OS-inbyggda trådar. Det beror på den specifika JVM-implementeringen.

I Java representeras en tråd som ett underordnat objekt till Thread. Denna klass kapslar in standardgängmekanismer. Trådar kan hanteras antingen direkt eller genom abstrakta mekanismer som Executor och samlingar från java.util.concurrent-paketet.

Kör en tråd

Det finns två sätt att starta en ny tråd:

  • Implementering av Runnable-gränssnittet
public class HelloRunnable implementerar Runnable { public void run () { System . ut . println ( "Hej från tråden!" ); } public static void main ( String [] args ) { ( ny tråd ( new HelloRunnable ())). start (); } }
  • Arv från trådklass
public class HelloThread utökar tråden { public void run () { System . ut . println ( "Hej från tråden!" ); } public static void main ( String [] args ) { ( new HelloThread ()). start (); } } Avbryter

Ett avbrott är en indikation till en tråd att den ska stoppa det pågående arbetet och göra något annat. En tråd kan skicka ett avbrott genom att anropa objektets interrupt()Thread -metod om den behöver avbryta dess associerade tråd. Avbrottsmekanismen implementeras med den interna flaggavbrottsstatusen (avbrottsflaggan) för klassen Thread. Att anropa Thread.interrupt() höjer denna flagga. Enligt konvention kommer alla metoder som slutar med en InterruptedException att återställa avbrottsflaggan. Det finns två sätt att kontrollera om denna flagga är inställd. Det första sättet är att anropa metoden bool isInterrupted() för trådobjektet , det andra sättet är att anropa den statiska bool metoden Thread.interrupted() . Den första metoden returnerar tillståndet för avbrottsflaggan och lämnar denna flagga orörd. Den andra metoden returnerar flaggans tillstånd och återställer den. Observera att Thread.interrupted()  är en statisk metod för klassen Thread, och att anropa den returnerar värdet på avbrottsflaggan för tråden från vilken den anropades.

Väntar på slutförande

Java tillhandahåller en mekanism som gör att en tråd kan vänta på att en annan tråd ska slutföras. För detta används metoden Thread.join() .

Demoner

I Java avslutas en process när dess sista tråd avslutas. Även om main()-metoden redan har slutförts, men trådarna den skapade fortfarande körs, kommer systemet att vänta på att de ska slutföras. Denna regel gäller dock inte för en speciell sorts tråd - demoner. Om den sista normala tråden i processen har avslutats och bara demontrådar finns kvar, kommer de att tvångsavslutas och processen avslutas. Oftast används demontrådar för att utföra bakgrundsuppgifter som servar en process under dess livstid.

Att förklara en tråd som en demon är ganska enkelt - du måste anropa dess setDaemon(true) -metod innan du startar tråden ; Du kan kontrollera om en tråd är en demon genom att anropa dess booleska isDaemon()- metod .

Undantag

Ett slängt och ohanterat undantag gör att tråden avslutas. Huvudtråden kommer automatiskt att skriva ut undantaget till konsolen, och användarskapade trådar kan bara göra det genom att registrera en hanterare. [1] [2]

Minnesmodell

Java-minnesmodellen [1] beskriver interaktionen mellan trådar genom minnet i programmeringsspråket Java. Ofta, på moderna datorer, exekveras inte kod i den ordning som den skrivs för hastighetens skull. Permutationen görs av kompilatorn , processorn och minnesundersystemet . Programmeringsspråket Java garanterar inte atomicitet av operationer och sekventiell konsistens vid läsning eller skrivning av fält för delade objekt. Denna lösning frigör kompilatorns händer och tillåter optimeringar (såsom registerallokering , borttagning av vanliga underuttryck och eliminering av redundanta läsoperationer ) baserat på permutation av minnesåtkomstoperationer. [3]

Synkronisering

Trådar kommunicerar genom att dela åtkomst till fält och objekt som refereras av fält. Denna form av kommunikation är extremt effektiv, men den gör två typer av fel möjliga: trådstörningar och minneskonsistensfel. För att förhindra att de uppstår finns det en synkroniseringsmekanism.

Omordning (omordning, omordning) manifesterar sig i felaktigt synkroniserade flertrådade program, där en tråd kan observera effekterna som produceras av andra trådar, och sådana program kan kanske upptäcka att de uppdaterade värdena för variabler blir synliga för andra trådar i en annan tråd. ordning än vad som anges i källkoden.

För att synkronisera trådar i Java används monitorer , som är en högnivåmekanism som tillåter endast en tråd åt gången att exekvera ett kodblock som skyddas av en monitor. Monitorernas beteende beaktas i termer av lås ; Varje objekt har ett lås kopplat till sig.

Synkronisering har flera aspekter. Det mest välkända är ömsesidig uteslutning - endast en tråd kan äga en monitor, sålunda innebär synkronisering på monitorn att när en tråd går in i ett synkroniserat block som skyddas av monitorn, kan ingen annan tråd komma in i blocket som skyddas av denna monitor förrän den första tråden lämnar det synkroniserade blocket.

Men synkronisering är mer än bara ömsesidig uteslutning. Synkronisering säkerställer att data som skrivs till minnet före eller inom ett synkroniserat block blir synligt för andra trådar som är synkroniserade på samma monitor. Efter att vi lämnat det synkroniserade blocket släpper vi monitorn, vilket har effekten att spola cachen till huvudminnet så att skrivningarna som gjorts av vår tråd kan vara synliga för andra trådar. Innan vi kan gå in i det synkroniserade blocket skaffar vi monitorn, vilket har effekten att ogiltigförklara den lokala processorcachen så att variablerna laddas från huvudminnet. Sedan kan vi se alla poster som gjorts synliga av den tidigare utgåvan av monitorn. (JSR 133)

En läs-skrivning på ett fält är en atomoperation om fältet antingen förklaras flyktigt eller skyddas av ett unikt lås som förvärvats före någon läs-skrivning.

Lås och synkroniserade block

Effekten av ömsesidig uteslutning och trådsynkronisering uppnås genom att ange ett synkroniserat block eller en metod som förvärvar låset implicit, eller genom att explicit förvärva låset (som ReentrantLockfrån paketet java.util.concurrent.locks). Båda tillvägagångssätten har samma effekt på minnesbeteendet. Om alla åtkomstförsök till ett visst fält skyddas av samma lås, är läs- och skrivoperationer för detta fält atomära .

Flyktiga fält

När det tillämpas på fält volatilegaranterar nyckelordet:

  1. (I alla versioner av Java) Tillgångar till volatile-variable ordnas globalt. Detta innebär att varje tråd som kommer åt volatile-fältet kommer att läsa dess värde innan den fortsätter, istället för (om möjligt) att använda det cachade värdet. (Åtkomster till volatile-variabler kan inte ordnas om med varandra, men de kan ordnas om med åtkomst till vanliga variabler. Detta förnekar användbarheten volatileav -fält som ett sätt att signalera från en tråd till en annan.)
  2. (I Java 5 och senare) Att skriva till ett fält har volatilesamma effekt på minnet som monitor release , medan läsning har samma effekt som monitor förvärv .  Åtkomst till fältet etablerar förhållandet " händer före " . [4] I huvudsak är denna relation en garanti för att allt som var synligt för tråden när den skrev till -fältet blir synligt för tråden när den läser . volatile AvolatilefBf

Volatile-fält är atomära. Att läsa från volatileett -fält har samma effekt som att skaffa ett lås: data i arbetsminnet förklaras ogiltiga, och volatile-fältets värde läses om från minnet. Att skriva till ett volatile-fält har samma effekt på minnet som att släppa ett lås: volatile-fältet skrivs omedelbart till minnet.

Slutliga fält

Ett fält som förklaras slutgiltigt kallas slutligt och kan inte ändras efter initialisering. De sista fälten för ett objekt initieras i dess konstruktor. Om konstruktören följer vissa enkla regler, kommer det korrekta värdet av det slutliga fältet att vara synligt för andra trådar utan synkronisering. En enkel regel är att denna referens inte får lämna konstruktören förrän den är klar.

Historik

Från och med JDK 1.2 inkluderar Java en standarduppsättning Java Collections Framework- samlingsklasser .

Doug Lee , som också bidrog till implementeringen av Java Collections Framework, utvecklade samtidighetspaketet , som inkluderar flera synkroniseringsprimitiver och ett stort antal samlingsrelaterade klasser. [5] Arbetet med det fortsatte som en del av JSR 166 [6] under ordförandeskapet av Doug Lee .

JDK 5.0- utgåvan innehöll många tillägg och förtydliganden till Java-samtidsmodellen. För första gången inkluderades samtidighets-API:erna som utvecklats av JSR 166 i JDK. JSR 133 gav stöd för väldefinierade atomoperationer i en multitrådad/multiprocessormiljö.

Både Java SE 6 och Java SE 7 ger ändringar och tillägg till JSR 166 API.

Se även

Anteckningar

  1. Oracle Interface Thread.UncaughtExceptionHandler . Hämtad 10 maj 2014. Arkiverad från originalet 12 maj 2014.
  2. Tyst tråd död från obehandlade undantag . readjava.com . Hämtad 10 maj 2014. Arkiverad från originalet 12 maj 2014.
  3. Herlihy, Maurice och Nir Shavit. "Konsten att programmera flera processorer." PODC. Vol. 6. 2006.
  4. Avsnitt 17.4.4: Synkroniseringsordning The Java® Language Specification, Java SE 7 Edition . Oracle Corporation (2013). Hämtad 12 maj 2013. Arkiverad från originalet 3 februari 2021.
  5. Doug Lee . Översikt över paket util.concurrent Release 1.3.4 . — « Obs: När J2SE 5.0 släpps går detta paket in i underhållsläge: Endast väsentliga korrigeringar kommer att släppas. J2SE5-paketet java.util.concurrent inkluderar förbättrade, mer effektiva, standardiserade versioner av huvudkomponenterna i detta paket. ". Hämtad 1 januari 2011. Arkiverad från originalet 18 december 2020.
  6. JSR 166: Concurrency Utilities (länk inte tillgänglig) . Hämtad 3 november 2015. Arkiverad från originalet 3 november 2016. 

Länkar

  • Goetz, Brian; Joshua Bloch; Joseph Bowbeer; Doug Lea; David Holmes Tim Peierls. Java Concurrency i praktiken  (neopr.) . - Addison Wesley , 2006. - ISBN 0-321-34960-1 .
  • Leah, Doug. Samtidig programmering i Java : Designprinciper och mönster  . - Addison Wesley , 1999. - ISBN 0-201-31009-0 .

Länkar till externa resurser