Trådning af modeller i Java

1. Introduktion

Ofte i vores applikationer er vi nødt til at være i stand til at gøre flere ting på samme tid. Vi kan opnå dette på flere måder, men nøglen blandt dem er at implementere multitasking i en eller anden form.

Multi-tasking betyder at køre flere opgaver på samme tid, hvor hver opgave udfører sit arbejde. Disse opgaver kører typisk alle på samme tid, læser og skriver den samme hukommelse og interagerer med de samme ressourcer, men laver forskellige ting.

2. Indfødte tråde

Standardmetoden til implementering af multi-tasking i Java er at bruge tråde. Trådning understøttes normalt ned til operativsystemet. Vi kalder tråde, der fungerer på dette niveau, "native threads".

Operativsystemet har nogle evner med threading, der ofte ikke er tilgængelige for vores applikationer, simpelthen på grund af hvor meget tættere det er på den underliggende hardware. Dette betyder, at udførelse af indfødte tråde typisk er mere effektiv. Disse tråde kortlægges direkte til udførelsestråde på computerens CPU - og operativsystemet styrer kortlægningen af ​​tråde på CPU-kerner.

Standard threading-modellen i Java, der dækker alle JVM-sprog, bruger native threads. Dette har været tilfældet siden Java 1.2 og er tilfældet uanset det underliggende system, som JVM kører på.

Dette betyder, at når som helst vi bruger nogen af ​​de standard threading-mekanismer i Java, så bruger vi native threads. Dette inkluderer java.lang.Tråd, java.util.concurrent.Executor, java.util.concurrent.ExecutorService, og så videre.

3. Grønne tråde

I softwareteknik, et alternativ til indfødte tråde er grønne tråde. Det er her, vi bruger tråde, men de kortlægges ikke direkte til operativsystemets tråde. I stedet styrer den underliggende arkitektur selve trådene og styrer, hvordan disse kortlægges til operativsystemets tråde.

Typisk fungerer dette ved at køre flere indfødte tråde og derefter tildele de grønne tråde til disse indfødte tråde til udførelse. Systemet kan derefter vælge, hvilke grønne tråde der er aktive til enhver tid, og hvilke indfødte tråde de er aktive på.

Dette lyder meget kompliceret, og det er det også. Men det er en komplikation, som vi generelt ikke behøver at bekymre os om. Den underliggende arkitektur tager sig af alt dette, og vi får brugt det som om det var en indfødt trådmodel.

Så hvorfor skulle vi gøre dette? Indfødte tråde er meget effektive at køre, men de har høje omkostninger omkring start og stop af dem. Grønne tråde hjælper med at undgå disse omkostninger og giver arkitekturen meget mere fleksibilitet. Hvis vi bruger relativt langvarige tråde, så er indfødte tråde meget effektive. For meget kortvarige job kan omkostningerne ved at starte dem opveje fordelen ved at bruge dem. I disse tilfælde kan grønne tråde blive mere effektive.

Desværre, Java har ikke indbygget support til grønne tråde.

Meget tidlige versioner brugte grønne tråde i stedet for native tråde som standard trådmodel. Dette ændrede sig i Java 1.2, og der har ikke været nogen support til det på JVM-niveau siden.

Det er også udfordrende at implementere grønne tråde i biblioteker, fordi de har brug for support på meget lavt niveau for at klare sig godt. Som sådan er et almindeligt anvendt alternativ fibre.

4. Fibre

Fibre er en alternativ form for multitrådning og ligner grønne tråde. I begge tilfælde bruger vi ikke indfødte tråde og bruger i stedet de underliggende systemkontroller, der kører til enhver tid. Den store forskel mellem grønne tråde og fibre er i niveauet for kontrol og specifikt hvem der har kontrol.

Grønne tråde er en form for forebyggende multitasking. Dette betyder, at den underliggende arkitektur er fuldstændig ansvarlig for at beslutte, hvilke tråde der udføres på et givet tidspunkt.

Dette betyder, at alle de sædvanlige problemer med threading gælder, hvor vi ikke ved noget om rækkefølgen af ​​vores threads, der udføres, eller hvilke der udføres på samme tid. Det betyder også, at det underliggende system skal være i stand til at stoppe og genstarte vores kode når som helst, potentielt midt i en metode eller endda en erklæring.

Fibre er i stedet en form for kooperativ multitasking, hvilket betyder at en kørende tråd fortsætter med at køre, indtil den signalerer, at den kan give til en anden. Det betyder, at det er vores ansvar for fibrene at samarbejde med hinanden. Dette sætter os i direkte kontrol over, hvornår fibrene kan standse udførelsen i stedet for at systemet beslutter dette for os.

Dette betyder også, at vi skal skrive vores kode på en måde, der giver mulighed for dette. Ellers fungerer det ikke. Hvis vores kode ikke har nogen afbrydelsespunkter, kan vi lige så godt ikke bruge fibre overhovedet.

Java har i øjeblikket ikke indbygget understøttelse af fibre. Der findes nogle biblioteker, der kan introducere dette til vores applikationer, herunder men ikke begrænset til:

4.1. Quasar

Quasar er et Java-bibliotek, der fungerer godt med ren Java og Kotlin og har en alternativ version, der fungerer med Clojure.

Det fungerer ved at have en Java-agent, der skal køre sammen med applikationen, og denne agent er ansvarlig for at styre fibrene og sikre, at de fungerer korrekt. Brugen af ​​en Java-agent betyder, at der ikke er behov for nogen specielle byggetrin.

Quasar kræver også, at Java 11 fungerer korrekt, så det kan begrænse de applikationer, der kan bruge det. Ældre versioner kan bruges på Java 8, men disse understøttes ikke aktivt.

4.2. Kilim

Kilim er et Java-bibliotek, der tilbyder meget lignende funktionalitet som Quasar, men gør det ved at bruge bytecode-vævning i stedet for en Java-agent. Det betyder, at det kan arbejde flere steder, men det gør byggeprocessen mere kompliceret.

Kilim fungerer med Java 7 og nyere og fungerer korrekt selv i scenarier, hvor en Java-agent ikke er en mulighed. For eksempel, hvis der allerede bruges en anden til instrumentering eller overvågning.

4.3. Project Loom

Project Loom er et eksperiment fra OpenJDK-projektet for at tilføje fibre til selve JVM snarere end som et tilføjelsesbibliotek. Dette giver os fordelene ved fibre i forhold til tråde. Ved at implementere det direkte på JVM kan det hjælpe med at undgå komplikationer, som Java-agenter og bytecode-vævning introducerer.

Der er ingen nuværende udgivelsesplan for Project Loom, men vi kan downloade binære filer med tidlig adgang lige nu for at se, hvordan det går. Men fordi det stadig er meget tidligt, skal vi være forsigtige med at stole på dette for enhver produktionskode.

5. Co-rutiner

Co-rutiner er et alternativ til gevind og fibre. Vi kan tænke på co-rutiner som fibre uden nogen form for planlægning. I stedet for at det underliggende system beslutter, hvilke opgaver der udføres til enhver tid, gør vores kode dette direkte.

Generelt skriver vi co-rutiner, så de giver på bestemte punkter i deres flow. Disse kan ses som pausepunkter i vores funktion, hvor den stopper med at arbejde og potentielt udsender et mellemliggende resultat. Når vi giver efter, stoppes vi derefter, indtil opkaldskoden beslutter at genstarte os af en eller anden grund. Dette betyder, at vores kaldekode styrer planlægningen af, hvornår denne kører.

Kotlin har indbygget support til co-rutiner indbygget i sit standardbibliotek. Der er flere andre Java-biblioteker, som vi også kan bruge til at implementere dem, hvis det ønskes.

6. Konklusion

Vi har set flere forskellige alternativer til multitasking i vores kode, der spænder fra de traditionelle native tråde til nogle meget lette alternativer. Hvorfor ikke prøve dem, næste gang en applikation har brug for samtidighed?